notionary 0.2.19__py3-none-any.whl → 0.2.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +18 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +65 -111
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +40 -66
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +52 -92
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +48 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +23 -95
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/models.py +0 -713
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
import re
|
3
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
4
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
5
|
+
|
6
|
+
|
7
|
+
class CaptionMixin:
|
8
|
+
"""Mixin to add caption parsing functionality to block elements."""
|
9
|
+
|
10
|
+
# Generic caption pattern - finds caption anywhere in text
|
11
|
+
CAPTION_PATTERN = re.compile(r"\(caption:([^)]*)\)")
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def extract_caption(cls, text: str) -> Optional[str]:
|
15
|
+
"""
|
16
|
+
Extract caption text from anywhere in the input text.
|
17
|
+
Returns only the caption content, preserving parentheses in content.
|
18
|
+
"""
|
19
|
+
# Look for (caption: followed by content followed by )
|
20
|
+
# Handle cases where caption content contains parentheses
|
21
|
+
caption_start = text.find("(caption:")
|
22
|
+
if caption_start == -1:
|
23
|
+
return None
|
24
|
+
|
25
|
+
# Find the matching closing parenthesis
|
26
|
+
# Start after "(caption:"
|
27
|
+
content_start = caption_start + 9 # len("(caption:")
|
28
|
+
paren_count = 1
|
29
|
+
pos = content_start
|
30
|
+
|
31
|
+
while pos < len(text) and paren_count > 0:
|
32
|
+
if text[pos] == "(":
|
33
|
+
paren_count += 1
|
34
|
+
elif text[pos] == ")":
|
35
|
+
paren_count -= 1
|
36
|
+
pos += 1
|
37
|
+
|
38
|
+
if paren_count == 0:
|
39
|
+
# Found matching closing parenthesis
|
40
|
+
return text[content_start : pos - 1]
|
41
|
+
|
42
|
+
return None
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def remove_caption(cls, text: str) -> str:
|
46
|
+
"""
|
47
|
+
Remove caption from text and return clean text.
|
48
|
+
Uses the same balanced parentheses logic as extract_caption.
|
49
|
+
"""
|
50
|
+
caption_start = text.find("(caption:")
|
51
|
+
if caption_start == -1:
|
52
|
+
return text.strip()
|
53
|
+
|
54
|
+
# Find the matching closing parenthesis
|
55
|
+
content_start = caption_start + 9 # len("(caption:")
|
56
|
+
paren_count = 1
|
57
|
+
pos = content_start
|
58
|
+
|
59
|
+
while pos < len(text) and paren_count > 0:
|
60
|
+
if text[pos] == "(":
|
61
|
+
paren_count += 1
|
62
|
+
elif text[pos] == ")":
|
63
|
+
paren_count -= 1
|
64
|
+
pos += 1
|
65
|
+
|
66
|
+
if paren_count == 0:
|
67
|
+
# Remove the entire caption including the outer parentheses
|
68
|
+
return (text[:caption_start] + text[pos:]).strip()
|
69
|
+
|
70
|
+
# Fallback to regex-based removal if balanced parsing fails
|
71
|
+
return cls.CAPTION_PATTERN.sub("", text).strip()
|
72
|
+
|
73
|
+
@classmethod
|
74
|
+
def build_caption_rich_text(cls, caption_text: str) -> list[RichTextObject]:
|
75
|
+
"""Return caption as canonical rich text list (with annotations)."""
|
76
|
+
if not caption_text:
|
77
|
+
return []
|
78
|
+
# IMPORTANT: use the same formatter used elsewhere in the app
|
79
|
+
return [RichTextObject.for_caption(caption_text)]
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
async def format_caption_for_markdown(
|
83
|
+
cls, caption_list: list[RichTextObject]
|
84
|
+
) -> str:
|
85
|
+
"""Convert rich text caption back to markdown format."""
|
86
|
+
if not caption_list:
|
87
|
+
return ""
|
88
|
+
# Preserve markdown formatting (bold, italic, etc.)
|
89
|
+
caption_text = await TextInlineFormatter.extract_text_with_formatting(
|
90
|
+
caption_list
|
91
|
+
)
|
92
|
+
return f"(caption:{caption_text})" if caption_text else ""
|
@@ -0,0 +1,174 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from notionary.blocks.types import BlockType
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from notionary.blocks.bookmark import BookmarkBlock, CreateBookmarkBlock
|
11
|
+
from notionary.blocks.breadcrumbs import BreadcrumbBlock, CreateBreadcrumbBlock
|
12
|
+
from notionary.blocks.bulleted_list import (
|
13
|
+
BulletedListItemBlock,
|
14
|
+
CreateBulletedListItemBlock,
|
15
|
+
)
|
16
|
+
from notionary.blocks.callout import CalloutBlock, CreateCalloutBlock
|
17
|
+
from notionary.blocks.child_page import ChildPageBlock, CreateChildPageBlock
|
18
|
+
from notionary.blocks.code import CodeBlock, CreateCodeBlock
|
19
|
+
from notionary.blocks.column import (
|
20
|
+
ColumnBlock,
|
21
|
+
ColumnListBlock,
|
22
|
+
CreateColumnBlock,
|
23
|
+
CreateColumnListBlock,
|
24
|
+
)
|
25
|
+
from notionary.blocks.divider import CreateDividerBlock, DividerBlock
|
26
|
+
from notionary.blocks.embed import CreateEmbedBlock, EmbedBlock
|
27
|
+
from notionary.blocks.equation import CreateEquationBlock, EquationBlock
|
28
|
+
from notionary.blocks.file import CreateFileBlock, FileBlock
|
29
|
+
from notionary.blocks.heading import (
|
30
|
+
CreateHeading1Block,
|
31
|
+
CreateHeading2Block,
|
32
|
+
CreateHeading3Block,
|
33
|
+
HeadingBlock,
|
34
|
+
)
|
35
|
+
from notionary.blocks.image_block import CreateImageBlock
|
36
|
+
from notionary.blocks.numbered_list import (
|
37
|
+
CreateNumberedListItemBlock,
|
38
|
+
NumberedListItemBlock,
|
39
|
+
)
|
40
|
+
from notionary.blocks.paragraph import CreateParagraphBlock, ParagraphBlock
|
41
|
+
from notionary.blocks.pdf import CreatePdfBlock
|
42
|
+
from notionary.blocks.quote import CreateQuoteBlock, QuoteBlock
|
43
|
+
from notionary.blocks.table import CreateTableBlock, TableBlock, TableRowBlock
|
44
|
+
from notionary.blocks.table_of_contents import (
|
45
|
+
CreateTableOfContentsBlock,
|
46
|
+
TableOfContentsBlock,
|
47
|
+
)
|
48
|
+
from notionary.blocks.todo import CreateToDoBlock, ToDoBlock
|
49
|
+
from notionary.blocks.toggle import CreateToggleBlock, ToggleBlock
|
50
|
+
from notionary.blocks.video import CreateVideoBlock
|
51
|
+
from notionary.blocks.child_database import ChildDatabaseBlock
|
52
|
+
|
53
|
+
|
54
|
+
class BlockChildrenResponse(BaseModel):
|
55
|
+
object: Literal["list"]
|
56
|
+
results: list["Block"]
|
57
|
+
next_cursor: Optional[str] = None
|
58
|
+
has_more: bool
|
59
|
+
type: Literal["block"]
|
60
|
+
block: dict = {}
|
61
|
+
request_id: str
|
62
|
+
|
63
|
+
|
64
|
+
class PageParent(BaseModel):
|
65
|
+
type: Literal["page_id"]
|
66
|
+
page_id: str
|
67
|
+
|
68
|
+
|
69
|
+
class DatabaseParent(BaseModel):
|
70
|
+
type: Literal["database_id"]
|
71
|
+
database_id: str
|
72
|
+
|
73
|
+
|
74
|
+
class BlockParent(BaseModel):
|
75
|
+
type: Literal["block_id"]
|
76
|
+
block_id: str
|
77
|
+
|
78
|
+
|
79
|
+
class WorkspaceParent(BaseModel):
|
80
|
+
type: Literal["workspace"]
|
81
|
+
workspace: bool = True
|
82
|
+
|
83
|
+
|
84
|
+
ParentObject = Union[PageParent, DatabaseParent, BlockParent, WorkspaceParent]
|
85
|
+
|
86
|
+
|
87
|
+
class PartialUser(BaseModel):
|
88
|
+
object: Literal["user"]
|
89
|
+
id: str
|
90
|
+
|
91
|
+
|
92
|
+
class Block(BaseModel):
|
93
|
+
object: Literal["block"]
|
94
|
+
id: str
|
95
|
+
parent: Optional[ParentObject] = None
|
96
|
+
type: BlockType
|
97
|
+
created_time: str
|
98
|
+
last_edited_time: str
|
99
|
+
created_by: PartialUser
|
100
|
+
last_edited_by: PartialUser
|
101
|
+
archived: bool = False
|
102
|
+
in_trash: bool = False
|
103
|
+
has_children: bool = False
|
104
|
+
|
105
|
+
children: Optional[list[Block]] = None
|
106
|
+
|
107
|
+
# Block type-specific content (only one will be populated based on type)
|
108
|
+
audio: Optional[FileBlock] = None
|
109
|
+
bookmark: Optional[BookmarkBlock] = None
|
110
|
+
breadcrumb: Optional[BreadcrumbBlock] = None
|
111
|
+
bulleted_list_item: Optional[BulletedListItemBlock] = None
|
112
|
+
callout: Optional[CalloutBlock] = None
|
113
|
+
child_page: Optional[ChildPageBlock] = None
|
114
|
+
code: Optional[CodeBlock] = None
|
115
|
+
column_list: Optional[ColumnListBlock] = None
|
116
|
+
column: Optional[ColumnBlock] = None
|
117
|
+
divider: Optional[DividerBlock] = None
|
118
|
+
embed: Optional[EmbedBlock] = None
|
119
|
+
equation: Optional[EquationBlock] = None
|
120
|
+
file: Optional[FileBlock] = None
|
121
|
+
heading_1: Optional[HeadingBlock] = None
|
122
|
+
heading_2: Optional[HeadingBlock] = None
|
123
|
+
heading_3: Optional[HeadingBlock] = None
|
124
|
+
image: Optional[FileBlock] = None
|
125
|
+
numbered_list_item: Optional[NumberedListItemBlock] = None
|
126
|
+
paragraph: Optional[ParagraphBlock] = None
|
127
|
+
quote: Optional[QuoteBlock] = None
|
128
|
+
table: Optional[TableBlock] = None
|
129
|
+
table_row: Optional[TableRowBlock] = None
|
130
|
+
to_do: Optional[ToDoBlock] = None
|
131
|
+
toggle: Optional[ToggleBlock] = None
|
132
|
+
video: Optional[FileBlock] = None
|
133
|
+
pdf: Optional[FileBlock] = None
|
134
|
+
table_of_contents: Optional[TableOfContentsBlock] = None
|
135
|
+
child_database: Optional[ChildDatabaseBlock] = None
|
136
|
+
|
137
|
+
def get_block_content(self) -> Optional[Any]:
|
138
|
+
"""Get the content object for this block based on its type."""
|
139
|
+
return getattr(self, self.type, None)
|
140
|
+
|
141
|
+
|
142
|
+
if TYPE_CHECKING:
|
143
|
+
BlockCreateRequest = Union[
|
144
|
+
CreateBookmarkBlock,
|
145
|
+
CreateBreadcrumbBlock,
|
146
|
+
CreateBulletedListItemBlock,
|
147
|
+
CreateCalloutBlock,
|
148
|
+
CreateChildPageBlock,
|
149
|
+
CreateCodeBlock,
|
150
|
+
CreateColumnListBlock,
|
151
|
+
CreateColumnBlock,
|
152
|
+
CreateDividerBlock,
|
153
|
+
CreateEmbedBlock,
|
154
|
+
CreateEquationBlock,
|
155
|
+
CreateFileBlock,
|
156
|
+
CreateHeading1Block,
|
157
|
+
CreateHeading2Block,
|
158
|
+
CreateHeading3Block,
|
159
|
+
CreateImageBlock,
|
160
|
+
CreateNumberedListItemBlock,
|
161
|
+
CreateParagraphBlock,
|
162
|
+
CreateQuoteBlock,
|
163
|
+
CreateToDoBlock,
|
164
|
+
CreateToggleBlock,
|
165
|
+
CreateVideoBlock,
|
166
|
+
CreateTableOfContentsBlock,
|
167
|
+
CreatePdfBlock,
|
168
|
+
CreateTableBlock,
|
169
|
+
]
|
170
|
+
BlockCreateResult = Union[BlockCreateRequest]
|
171
|
+
else:
|
172
|
+
# at runtime there are no typings anyway
|
173
|
+
BlockCreateRequest = Any
|
174
|
+
BlockCreateResult = Any
|
@@ -1,7 +1,17 @@
|
|
1
|
-
from .numbered_list_element import NumberedListElement
|
2
|
-
from .numbered_list_markdown_node import
|
1
|
+
from notionary.blocks.numbered_list.numbered_list_element import NumberedListElement
|
2
|
+
from notionary.blocks.numbered_list.numbered_list_markdown_node import (
|
3
|
+
NumberedListMarkdownBlockParams,
|
4
|
+
NumberedListMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.numbered_list.numbered_list_models import (
|
7
|
+
CreateNumberedListItemBlock,
|
8
|
+
NumberedListItemBlock,
|
9
|
+
)
|
3
10
|
|
4
11
|
__all__ = [
|
5
12
|
"NumberedListElement",
|
13
|
+
"NumberedListItemBlock",
|
14
|
+
"CreateNumberedListItemBlock",
|
6
15
|
"NumberedListMarkdownNode",
|
16
|
+
"NumberedListMarkdownBlockParams",
|
7
17
|
]
|
@@ -1,73 +1,65 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
3
|
-
|
4
|
-
from notionary.blocks import
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
9
|
+
from notionary.blocks.numbered_list.numbered_list_models import (
|
10
|
+
CreateNumberedListItemBlock,
|
11
|
+
NumberedListItemBlock,
|
8
12
|
)
|
9
|
-
from notionary.blocks.
|
13
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
14
|
+
from notionary.blocks.types import BlockColor
|
10
15
|
|
11
16
|
|
12
|
-
class NumberedListElement(
|
13
|
-
"""
|
17
|
+
class NumberedListElement(BaseBlockElement):
|
18
|
+
"""Converts between Markdown numbered lists and Notion numbered list items."""
|
14
19
|
|
15
|
-
|
16
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
17
|
-
"""Convert markdown numbered list item to Notion block."""
|
18
|
-
pattern = re.compile(r"^\s*(\d+)\.\s+(.+)$")
|
19
|
-
numbered_match = pattern.match(text)
|
20
|
-
if not numbered_match:
|
21
|
-
return None
|
20
|
+
PATTERN = re.compile(r"^\s*(\d+)\.\s+(.+)$")
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
27
|
-
|
28
|
-
return {
|
29
|
-
"type": "numbered_list_item",
|
30
|
-
"numbered_list_item": {"rich_text": rich_text, "color": "default"},
|
31
|
-
}
|
22
|
+
@classmethod
|
23
|
+
def match_notion(cls, block: Block) -> bool:
|
24
|
+
return block.type == BlockType.NUMBERED_LIST_ITEM and block.numbered_list_item
|
32
25
|
|
33
26
|
@classmethod
|
34
|
-
def
|
35
|
-
"""Convert
|
36
|
-
|
27
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
28
|
+
"""Convert markdown numbered list item to Notion NumberedListItemBlock."""
|
29
|
+
match = cls.PATTERN.match(text.strip())
|
30
|
+
if not match:
|
37
31
|
return None
|
38
32
|
|
39
|
-
|
40
|
-
|
33
|
+
content = match.group(2)
|
34
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content)
|
41
35
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
"""Check if this element can handle the given markdown text."""
|
47
|
-
pattern = re.compile(r"^\s*\d+\.\s+(.+)$")
|
48
|
-
return bool(pattern.match(text))
|
36
|
+
numbered_list_content = NumberedListItemBlock(
|
37
|
+
rich_text=rich_text, color=BlockColor.DEFAULT
|
38
|
+
)
|
39
|
+
return CreateNumberedListItemBlock(numbered_list_item=numbered_list_content)
|
49
40
|
|
41
|
+
# FIX: Roundtrip conversions will never work this way here
|
50
42
|
@classmethod
|
51
|
-
def
|
52
|
-
|
53
|
-
|
43
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
44
|
+
if block.type != BlockType.NUMBERED_LIST_ITEM or not block.numbered_list_item:
|
45
|
+
return None
|
54
46
|
|
55
|
-
|
56
|
-
|
57
|
-
return
|
47
|
+
rich = block.numbered_list_item.rich_text
|
48
|
+
content = await TextInlineFormatter.extract_text_with_formatting(rich)
|
49
|
+
return f"1. {content}"
|
58
50
|
|
59
51
|
@classmethod
|
60
|
-
def
|
61
|
-
"""
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
"
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
.
|
52
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
53
|
+
"""Get system prompt information for numbered list blocks."""
|
54
|
+
return BlockElementMarkdownInformation(
|
55
|
+
block_type=cls.__name__,
|
56
|
+
description="Numbered list items create ordered lists with sequential numbering",
|
57
|
+
syntax_examples=[
|
58
|
+
"1. First item",
|
59
|
+
"2. Second item",
|
60
|
+
"3. Third item",
|
61
|
+
"1. Item with **bold text**",
|
62
|
+
"1. Item with *italic text*",
|
63
|
+
],
|
64
|
+
usage_guidelines="Use numbers followed by periods to create ordered lists. Supports inline formatting like bold, italic, and links. Numbering is automatically handled by Notion.",
|
73
65
|
)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing_extensions import Literal
|
3
|
+
|
4
|
+
from notionary.blocks.models import Block
|
5
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
6
|
+
from notionary.blocks.types import BlockColor
|
7
|
+
|
8
|
+
|
9
|
+
class NumberedListItemBlock(BaseModel):
|
10
|
+
rich_text: list[RichTextObject]
|
11
|
+
color: BlockColor = BlockColor.DEFAULT
|
12
|
+
children: list[Block] = Field(default_factory=list)
|
13
|
+
|
14
|
+
|
15
|
+
class CreateNumberedListItemBlock(BaseModel):
|
16
|
+
type: Literal["numbered_list_item"] = "numbered_list_item"
|
17
|
+
numbered_list_item: NumberedListItemBlock
|
@@ -1,7 +1,17 @@
|
|
1
|
-
from .paragraph_element import ParagraphElement
|
2
|
-
from .paragraph_markdown_node import
|
1
|
+
from notionary.blocks.paragraph.paragraph_element import ParagraphElement
|
2
|
+
from notionary.blocks.paragraph.paragraph_markdown_node import (
|
3
|
+
ParagraphMarkdownBlockParams,
|
4
|
+
ParagraphMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
7
|
+
CreateParagraphBlock,
|
8
|
+
ParagraphBlock,
|
9
|
+
)
|
3
10
|
|
4
11
|
__all__ = [
|
5
12
|
"ParagraphElement",
|
13
|
+
"ParagraphBlock",
|
14
|
+
"CreateParagraphBlock",
|
6
15
|
"ParagraphMarkdownNode",
|
16
|
+
"ParagraphMarkdownBlockParams",
|
7
17
|
]
|
@@ -1,84 +1,58 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
from notionary.blocks import (
|
5
|
-
ElementPromptContent,
|
6
|
-
ElementPromptBuilder,
|
7
|
-
NotionBlockResult,
|
8
|
-
)
|
9
|
-
from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
|
3
|
+
from typing import Optional
|
10
4
|
|
5
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
6
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
7
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
8
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
9
|
+
CreateParagraphBlock,
|
10
|
+
ParagraphBlock,
|
11
|
+
)
|
12
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
13
|
+
from notionary.blocks.types import BlockColor, BlockType
|
11
14
|
|
12
|
-
class ParagraphElement(NotionBlockElement):
|
13
|
-
"""Handles conversion between Markdown paragraphs and Notion paragraph blocks."""
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
Paragraphs are essentially any text that isn't matched by other block elements.
|
20
|
-
Since paragraphs are the fallback element, this always returns True.
|
21
|
-
"""
|
22
|
-
return True
|
16
|
+
class ParagraphElement(BaseBlockElement):
|
17
|
+
"""
|
18
|
+
Handles conversion between Markdown paragraphs and Notion paragraph blocks.
|
19
|
+
"""
|
23
20
|
|
24
21
|
@classmethod
|
25
|
-
def match_notion(cls, block:
|
26
|
-
|
27
|
-
return block.get("type") == "paragraph"
|
22
|
+
def match_notion(cls, block: Block) -> bool:
|
23
|
+
return block.type == "paragraph" and block.paragraph
|
28
24
|
|
29
25
|
@classmethod
|
30
|
-
def markdown_to_notion(cls, text: str) ->
|
31
|
-
"""Convert markdown
|
26
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
27
|
+
"""Convert markdown text to a Notion ParagraphBlock."""
|
32
28
|
if not text.strip():
|
33
29
|
return None
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
},
|
40
|
-
}
|
31
|
+
rich = await TextInlineFormatter.parse_inline_formatting(text)
|
32
|
+
|
33
|
+
paragraph_content = ParagraphBlock(rich_text=rich, color=BlockColor.DEFAULT)
|
34
|
+
return CreateParagraphBlock(paragraph=paragraph_content)
|
41
35
|
|
42
36
|
@classmethod
|
43
|
-
def notion_to_markdown(cls, block:
|
44
|
-
|
45
|
-
if block.get("type") != "paragraph":
|
37
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
38
|
+
if block.type != BlockType.PARAGRAPH or not block.paragraph:
|
46
39
|
return None
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
text = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
52
|
-
return text if text else None
|
53
|
-
|
54
|
-
@classmethod
|
55
|
-
def is_multiline(cls) -> bool:
|
56
|
-
return False
|
41
|
+
rich_list = block.paragraph.rich_text
|
42
|
+
markdown = await TextInlineFormatter.extract_text_with_formatting(rich_list)
|
43
|
+
return markdown or None
|
57
44
|
|
58
45
|
@classmethod
|
59
|
-
def
|
60
|
-
"""
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
"
|
68
|
-
"
|
69
|
-
|
70
|
-
.
|
71
|
-
"Use for normal text content. Paragraphs are the default block type when no specific formatting is applied. "
|
72
|
-
"Apply inline formatting to highlight key points or provide links to resources."
|
73
|
-
)
|
74
|
-
.with_syntax("Just write text normally without any special prefix")
|
75
|
-
.with_examples(
|
76
|
-
[
|
77
|
-
"This is a simple paragraph with plain text.",
|
78
|
-
"This paragraph has **bold** and *italic* formatting.",
|
79
|
-
"You can include [links](https://example.com) or `inline code`.",
|
80
|
-
"Advanced formatting: ~~strikethrough~~ and __underlined text__.",
|
81
|
-
]
|
82
|
-
)
|
83
|
-
.build()
|
46
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
47
|
+
"""Get system prompt information for paragraph blocks."""
|
48
|
+
return BlockElementMarkdownInformation(
|
49
|
+
block_type=cls.__name__,
|
50
|
+
description="Paragraph blocks contain regular text content with optional inline formatting",
|
51
|
+
syntax_examples=[
|
52
|
+
"This is a simple paragraph.",
|
53
|
+
"Paragraph with **bold text** and *italic text*.",
|
54
|
+
"Paragraph with [link](https://example.com) and `code`.",
|
55
|
+
"Multiple sentences in one paragraph. Each sentence flows naturally.",
|
56
|
+
],
|
57
|
+
usage_guidelines="Use for regular text content. Supports inline formatting: **bold**, *italic*, `code`, [links](url). Default block type for plain text.",
|
84
58
|
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
6
|
+
from notionary.blocks.types import BlockColor
|
7
|
+
|
8
|
+
|
9
|
+
class ParagraphBlock(BaseModel):
|
10
|
+
rich_text: list[RichTextObject]
|
11
|
+
color: BlockColor = BlockColor.DEFAULT.value
|
12
|
+
|
13
|
+
|
14
|
+
class CreateParagraphBlock(BaseModel):
|
15
|
+
type: Literal["paragraph"] = "paragraph"
|
16
|
+
paragraph: ParagraphBlock
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from notionary.blocks.pdf.pdf_element import PdfElement
|
2
|
+
from notionary.blocks.pdf.pdf_markdown_node import (
|
3
|
+
PdfMarkdownNode,
|
4
|
+
PdfMarkdownNodeParams,
|
5
|
+
)
|
6
|
+
from notionary.blocks.pdf.pdf_models import CreatePdfBlock
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"PdfElement",
|
10
|
+
"CreatePdfBlock",
|
11
|
+
"PdfMarkdownNode",
|
12
|
+
"PdfMarkdownNodeParams",
|
13
|
+
]
|