fix notion write tools and add retry wrapper

This commit is contained in:
CREDO23 2026-02-11 17:18:59 +02:00
parent 8649ca7ac0
commit 04ea40f0c8
4 changed files with 101 additions and 46 deletions

View file

@ -1,3 +1,4 @@
import logging
from typing import Any from typing import Any
from langchain_core.tools import tool from langchain_core.tools import tool
@ -5,6 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.connectors.notion_history import NotionHistoryConnector from app.connectors.notion_history import NotionHistoryConnector
logger = logging.getLogger(__name__)
def create_create_notion_page_tool( def create_create_notion_page_tool(
db_session: AsyncSession | None = None, db_session: AsyncSession | None = None,
@ -53,7 +56,10 @@ def create_create_notion_page_tool(
- "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'" - "Create a Notion page titled 'Meeting Notes' with content 'Discussed project timeline'"
- "Save this to Notion with title 'Research Summary'" - "Save this to Notion with title 'Research Summary'"
""" """
logger.info(f"create_notion_page called: title='{title}', parent_page_id={parent_page_id}")
if db_session is None or search_space_id is None: if db_session is None or search_space_id is None:
logger.error("Notion tool not properly configured - missing db_session or search_space_id")
return { return {
"status": "error", "status": "error",
"message": "Notion tool not properly configured. Please contact support.", "message": "Notion tool not properly configured. Please contact support.",
@ -61,7 +67,8 @@ def create_create_notion_page_tool(
try: try:
# Get connector ID if not provided # Get connector ID if not provided
if connector_id is None: actual_connector_id = connector_id
if actual_connector_id is None:
from sqlalchemy.future import select from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType from app.db import SearchSourceConnector, SearchSourceConnectorType
@ -71,37 +78,41 @@ def create_create_notion_page_tool(
SearchSourceConnector.search_space_id == search_space_id, SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.connector_type SearchSourceConnector.connector_type
== SearchSourceConnectorType.NOTION_CONNECTOR, == SearchSourceConnectorType.NOTION_CONNECTOR,
SearchSourceConnector.is_enabled == True,
) )
) )
connector = result.scalars().first() connector = result.scalars().first()
if not connector: if not connector:
logger.warning(f"No Notion connector found for search_space_id={search_space_id}")
return { return {
"status": "error", "status": "error",
"message": "No Notion connector found. Please connect Notion in your workspace settings.", "message": "No Notion connector found. Please connect Notion in your workspace settings.",
} }
connector_id = connector.id actual_connector_id = connector.id
logger.info(f"Found Notion connector: id={actual_connector_id}")
# Create connector instance # Create connector instance
notion_connector = NotionHistoryConnector( notion_connector = NotionHistoryConnector(
session=db_session, session=db_session,
connector_id=connector_id, connector_id=actual_connector_id,
) )
# Create the page # Create the page
result = await notion_connector.create_page( result = await notion_connector.create_page(
title=title, content=content, parent_page_id=parent_page_id title=title, content=content, parent_page_id=parent_page_id
) )
logger.info(f"create_page result: {result.get('status')} - {result.get('message', '')}")
return result return result
except ValueError as e: except ValueError as e:
logger.error(f"ValueError creating Notion page: {e}")
return { return {
"status": "error", "status": "error",
"message": str(e), "message": str(e),
} }
except Exception as e: except Exception as e:
logger.error(f"Unexpected error creating Notion page: {e}", exc_info=True)
return { return {
"status": "error", "status": "error",
"message": f"Unexpected error creating Notion page: {str(e)}", "message": f"Unexpected error creating Notion page: {str(e)}",

View file

@ -65,7 +65,6 @@ def create_delete_notion_page_tool(
SearchSourceConnector.search_space_id == search_space_id, SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.connector_type SearchSourceConnector.connector_type
== SearchSourceConnectorType.NOTION_CONNECTOR, == SearchSourceConnectorType.NOTION_CONNECTOR,
SearchSourceConnector.is_enabled == True,
) )
) )
connector = result.scalars().first() connector = result.scalars().first()

View file

@ -77,7 +77,6 @@ def create_update_notion_page_tool(
SearchSourceConnector.search_space_id == search_space_id, SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.connector_type SearchSourceConnector.connector_type
== SearchSourceConnectorType.NOTION_CONNECTOR, == SearchSourceConnectorType.NOTION_CONNECTOR,
SearchSourceConnector.is_enabled == True,
) )
) )
connector = result.scalars().first() connector = result.scalars().first()

View file

@ -10,7 +10,6 @@ from sqlalchemy.future import select
from app.config import config from app.config import config
from app.db import SearchSourceConnector from app.db import SearchSourceConnector
from app.routes.notion_add_connector_route import refresh_notion_token
from app.schemas.notion_auth_credentials import NotionAuthCredentialsBase from app.schemas.notion_auth_credentials import NotionAuthCredentialsBase
from app.utils.oauth_security import TokenEncryption from app.utils.oauth_security import TokenEncryption
@ -219,6 +218,7 @@ class NotionHistoryConnector:
) )
# Refresh token # Refresh token
from app.routes.notion_add_connector_route import refresh_notion_token
connector = await refresh_notion_token(self._session, connector) connector = await refresh_notion_token(self._session, connector)
# Reload credentials after refresh # Reload credentials after refresh
@ -782,6 +782,34 @@ class NotionHistoryConnector:
# WRITE OPERATIONS (create, update, delete pages) # WRITE OPERATIONS (create, update, delete pages)
# ========================================================================= # =========================================================================
async def _get_first_accessible_parent(self) -> str | None:
"""
Get the first accessible page ID that can be used as a parent.
Returns:
Page ID string, or None if no accessible pages found
"""
try:
notion = await self._get_client()
# Search for pages, get most recently edited first
response = await self._api_call_with_retry(
notion.search,
filter={"property": "object", "value": "page"},
sort={"direction": "descending", "timestamp": "last_edited_time"},
page_size=1, # We only need the first one
)
results = response.get("results", [])
if results:
return results[0]["id"]
return None
except Exception as e:
logger.error(f"Error finding accessible parent page: {e}")
return None
def _markdown_to_blocks(self, markdown: str) -> list[dict[str, Any]]: def _markdown_to_blocks(self, markdown: str) -> list[dict[str, Any]]:
""" """
Convert markdown content to Notion blocks. Convert markdown content to Notion blocks.
@ -884,40 +912,40 @@ class NotionHistoryConnector:
APIResponseError: If Notion API returns an error APIResponseError: If Notion API returns an error
""" """
try: try:
logger.info(f"Creating Notion page: title='{title}', parent_page_id={parent_page_id}")
# Get Notion client # Get Notion client
notion = await self._get_notion_client() notion = await self._get_client()
# Convert markdown content to Notion blocks # Convert markdown content to Notion blocks
children = self._markdown_to_blocks(content) children = self._markdown_to_blocks(content)
# Prepare parent # Prepare parent - find first available page if not provided
if parent_page_id: if not parent_page_id:
parent = {"type": "page_id", "page_id": parent_page_id} logger.info("No parent_page_id provided, searching for first accessible page...")
else: parent_page_id = await self._get_first_accessible_parent()
# Try to use workspace root if not parent_page_id:
# Note: This requires proper permissions logger.warning("No accessible parent pages found")
result = await self._session.execute( return {
select(SearchSourceConnector).filter( "status": "error",
SearchSourceConnector.id == self._connector_id "message": "Could not find any accessible Notion pages to use as parent. "
) "Please make sure your Notion integration has access to at least one page.",
)
connector = result.scalars().first()
if connector and connector.config.get("workspace_id"):
parent = {"type": "workspace", "workspace": True}
else:
raise ValueError(
"parent_page_id is required. "
"Please specify a parent page where the new page should be created."
)
# Create the page
response = await notion.pages.create(
parent=parent,
properties={
"title": {
"title": [{"type": "text", "text": {"content": title}}]
} }
}, logger.info(f"Using parent_page_id: {parent_page_id}")
parent = {"type": "page_id", "page_id": parent_page_id}
# Create the page with standard title property
properties = {
"title": {
"title": [{"type": "text", "text": {"content": title}}]
}
}
response = await self._api_call_with_retry(
notion.pages.create,
parent=parent,
properties=properties,
children=children[:100], # Notion API limit: 100 blocks per request children=children[:100], # Notion API limit: 100 blocks per request
) )
@ -928,8 +956,10 @@ class NotionHistoryConnector:
if len(children) > 100: if len(children) > 100:
for i in range(100, len(children), 100): for i in range(100, len(children), 100):
batch = children[i : i + 100] batch = children[i : i + 100]
await notion.blocks.children.append( await self._api_call_with_retry(
block_id=page_id, children=batch notion.blocks.children.append,
block_id=page_id,
children=batch
) )
return { return {
@ -972,11 +1002,12 @@ class NotionHistoryConnector:
APIResponseError: If Notion API returns an error APIResponseError: If Notion API returns an error
""" """
try: try:
notion = await self._get_notion_client() notion = await self._get_client()
# Update title if provided # Update title if provided
if title: if title:
await notion.pages.update( await self._api_call_with_retry(
notion.pages.update,
page_id=page_id, page_id=page_id,
properties={ properties={
"title": { "title": {
@ -988,22 +1019,33 @@ class NotionHistoryConnector:
# Update content if provided # Update content if provided
if content: if content:
# First, get existing blocks # First, get existing blocks
existing_blocks = await notion.blocks.children.list(block_id=page_id) existing_blocks = await self._api_call_with_retry(
notion.blocks.children.list,
block_id=page_id
)
# Delete existing blocks # Delete existing blocks
for block in existing_blocks.get("results", []): for block in existing_blocks.get("results", []):
await notion.blocks.delete(block_id=block["id"]) await self._api_call_with_retry(
notion.blocks.delete,
block_id=block["id"]
)
# Add new content # Add new content
children = self._markdown_to_blocks(content) children = self._markdown_to_blocks(content)
for i in range(0, len(children), 100): for i in range(0, len(children), 100):
batch = children[i : i + 100] batch = children[i : i + 100]
await notion.blocks.children.append( await self._api_call_with_retry(
block_id=page_id, children=batch notion.blocks.children.append,
block_id=page_id,
children=batch
) )
# Get updated page # Get updated page
response = await notion.pages.retrieve(page_id=page_id) response = await self._api_call_with_retry(
notion.pages.retrieve,
page_id=page_id
)
page_url = response["url"] page_url = response["url"]
page_title = response["properties"]["title"]["title"][0]["text"]["content"] page_title = response["properties"]["title"]["title"][0]["text"]["content"]
@ -1045,10 +1087,14 @@ class NotionHistoryConnector:
APIResponseError: If Notion API returns an error APIResponseError: If Notion API returns an error
""" """
try: try:
notion = await self._get_notion_client() notion = await self._get_client()
# Archive the page (Notion's way of "deleting") # Archive the page (Notion's way of "deleting")
response = await notion.pages.update(page_id=page_id, archived=True) response = await self._api_call_with_retry(
notion.pages.update,
page_id=page_id,
archived=True
)
page_title = "Unknown" page_title = "Unknown"
try: try: