notionary 0.2.23__py3-none-any.whl → 0.2.24__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 +1 -1
- notionary/blocks/__init__.py +3 -1
- notionary/blocks/audio/__init__.py +0 -2
- notionary/blocks/audio/audio_element.py +92 -49
- notionary/blocks/audio/audio_markdown_node.py +4 -17
- notionary/blocks/bookmark/__init__.py +0 -2
- notionary/blocks/bookmark/bookmark_markdown_node.py +5 -21
- notionary/blocks/breadcrumbs/__init__.py +0 -2
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +2 -21
- notionary/blocks/bulleted_list/__init__.py +0 -2
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +3 -17
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -1
- notionary/blocks/callout/__init__.py +0 -2
- notionary/blocks/callout/callout_markdown_node.py +4 -18
- notionary/blocks/callout/callout_models.py +3 -4
- notionary/blocks/code/code_markdown_node.py +5 -19
- notionary/blocks/column/__init__.py +0 -4
- notionary/blocks/column/column_list_markdown_node.py +3 -19
- notionary/blocks/column/column_markdown_node.py +4 -21
- notionary/blocks/divider/__init__.py +0 -2
- notionary/blocks/divider/divider_markdown_node.py +2 -16
- notionary/blocks/embed/__init__.py +0 -2
- notionary/blocks/embed/embed_markdown_node.py +4 -17
- notionary/blocks/equation/__init__.py +0 -1
- notionary/blocks/equation/equation_element_markdown_node.py +3 -15
- notionary/blocks/file/__init__.py +0 -2
- notionary/blocks/file/file_element.py +67 -46
- notionary/blocks/file/file_element_markdown_node.py +4 -17
- notionary/blocks/heading/__init__.py +0 -2
- notionary/blocks/heading/heading_markdown_node.py +5 -19
- notionary/blocks/heading/heading_models.py +3 -3
- notionary/blocks/image_block/__init__.py +0 -2
- notionary/blocks/image_block/image_element.py +66 -25
- notionary/blocks/image_block/image_markdown_node.py +5 -20
- notionary/{markdown → blocks/markdown}/markdown_builder.py +29 -233
- notionary/blocks/markdown/markdown_node.py +25 -0
- notionary/blocks/mixins/file_upload/__init__.py +3 -0
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +320 -0
- notionary/blocks/numbered_list/__init__.py +0 -1
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -17
- notionary/blocks/numbered_list/numbered_list_models.py +3 -3
- notionary/blocks/paragraph/__init__.py +0 -2
- notionary/blocks/paragraph/paragraph_markdown_node.py +3 -13
- notionary/blocks/pdf/__init__.py +0 -2
- notionary/blocks/pdf/pdf_element.py +81 -32
- notionary/blocks/pdf/pdf_markdown_node.py +5 -18
- notionary/blocks/quote/__init__.py +0 -2
- notionary/blocks/quote/quote_markdown_node.py +3 -13
- notionary/blocks/registry/__init__.py +1 -2
- notionary/blocks/registry/block_registry.py +116 -61
- notionary/blocks/table/__init__.py +0 -2
- notionary/blocks/table/table_markdown_node.py +17 -16
- notionary/blocks/table_of_contents/__init__.py +0 -2
- notionary/blocks/table_of_contents/table_of_contents_element.py +27 -15
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +3 -17
- notionary/blocks/table_of_contents/table_of_contents_models.py +2 -2
- notionary/blocks/todo/__init__.py +0 -2
- notionary/blocks/todo/todo_markdown_node.py +9 -20
- notionary/blocks/todo/todo_models.py +2 -3
- notionary/blocks/toggle/__init__.py +0 -2
- notionary/blocks/toggle/toggle_markdown_node.py +5 -19
- notionary/blocks/toggleable_heading/__init__.py +0 -2
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +6 -23
- notionary/blocks/video/__init__.py +0 -2
- notionary/blocks/video/video_element.py +110 -34
- notionary/blocks/video/video_markdown_node.py +4 -15
- notionary/comments/client.py +1 -1
- notionary/file_upload/client.py +3 -2
- notionary/file_upload/models.py +10 -1
- notionary/file_upload/notion_file_upload.py +5 -5
- notionary/page/markdown_whitespace_processor.py +129 -0
- notionary/page/notion_page.py +35 -40
- notionary/page/page_content_deleting_service.py +1 -1
- notionary/page/page_content_writer.py +32 -129
- notionary/page/page_context.py +0 -5
- notionary/page/reader/handler/column_list_renderer.py +2 -2
- notionary/page/reader/handler/column_renderer.py +2 -2
- notionary/page/reader/handler/line_renderer.py +2 -2
- notionary/page/reader/handler/toggle_renderer.py +2 -2
- notionary/page/reader/handler/toggleable_heading_renderer.py +2 -2
- notionary/page/writer/handler/toggle_handler.py +8 -4
- notionary/page/writer/handler/toggleable_heading_handler.py +3 -2
- notionary/page/writer/markdown_to_notion_converter.py +74 -30
- notionary/schemas/__init__.py +3 -0
- notionary/schemas/base.py +73 -0
- notionary/shared/__init__.py +1 -3
- {notionary-0.2.23.dist-info → notionary-0.2.24.dist-info}/METADATA +16 -1
- {notionary-0.2.23.dist-info → notionary-0.2.24.dist-info}/RECORD +91 -93
- notionary/blocks/guards.py +0 -22
- notionary/blocks/registry/block_registry_builder.py +0 -264
- notionary/markdown/makdown_document_model.py +0 -0
- notionary/markdown/markdown_document_model.py +0 -228
- notionary/markdown/markdown_node.py +0 -30
- notionary/models/notion_database_response.py +0 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -73
- notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
- /notionary/{markdown/___init__.py → blocks/markdown/markdown_document_model.py} +0 -0
- {notionary-0.2.23.dist-info → notionary-0.2.24.dist-info}/LICENSE +0 -0
- {notionary-0.2.23.dist-info → notionary-0.2.24.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
|
|
1
|
-
from pydantic import BaseModel
|
2
|
-
from
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from typing import Literal, Optional
|
3
3
|
|
4
4
|
from notionary.blocks.models import Block
|
5
5
|
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
@@ -9,7 +9,7 @@ from notionary.blocks.types import BlockColor
|
|
9
9
|
class NumberedListItemBlock(BaseModel):
|
10
10
|
rich_text: list[RichTextObject]
|
11
11
|
color: BlockColor = BlockColor.DEFAULT
|
12
|
-
children: list[Block] =
|
12
|
+
children: Optional[list[Block]] = None
|
13
13
|
|
14
14
|
|
15
15
|
class CreateNumberedListItemBlock(BaseModel):
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from notionary.blocks.paragraph.paragraph_element import ParagraphElement
|
2
2
|
from notionary.blocks.paragraph.paragraph_markdown_node import (
|
3
|
-
ParagraphMarkdownBlockParams,
|
4
3
|
ParagraphMarkdownNode,
|
5
4
|
)
|
6
5
|
from notionary.blocks.paragraph.paragraph_models import (
|
@@ -13,5 +12,4 @@ __all__ = [
|
|
13
12
|
"ParagraphBlock",
|
14
13
|
"CreateParagraphBlock",
|
15
14
|
"ParagraphMarkdownNode",
|
16
|
-
"ParagraphMarkdownBlockParams",
|
17
15
|
]
|
@@ -1,26 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
-
|
7
|
-
|
8
|
-
class ParagraphMarkdownBlockParams(BaseModel):
|
9
|
-
text: str
|
3
|
+
from notionary.blocks.markdown.markdown_node import MarkdownNode
|
10
4
|
|
11
5
|
|
12
6
|
class ParagraphMarkdownNode(MarkdownNode):
|
13
7
|
"""
|
8
|
+
Enhanced Paragraph node with Pydantic integration.
|
14
9
|
Programmatic interface for creating Markdown paragraphs.
|
15
10
|
Paragraphs are standard text without special block formatting.
|
16
11
|
"""
|
17
12
|
|
18
|
-
|
19
|
-
self.text = text
|
20
|
-
|
21
|
-
@classmethod
|
22
|
-
def from_params(cls, params: ParagraphMarkdownBlockParams) -> ParagraphMarkdownNode:
|
23
|
-
return cls(text=params.text)
|
13
|
+
text: str
|
24
14
|
|
25
15
|
def to_markdown(self) -> str:
|
26
16
|
return self.text
|
notionary/blocks/pdf/__init__.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from notionary.blocks.pdf.pdf_element import PdfElement
|
2
2
|
from notionary.blocks.pdf.pdf_markdown_node import (
|
3
3
|
PdfMarkdownNode,
|
4
|
-
PdfMarkdownNodeParams,
|
5
4
|
)
|
6
5
|
from notionary.blocks.pdf.pdf_models import CreatePdfBlock
|
7
6
|
|
@@ -9,5 +8,4 @@ __all__ = [
|
|
9
8
|
"PdfElement",
|
10
9
|
"CreatePdfBlock",
|
11
10
|
"PdfMarkdownNode",
|
12
|
-
"PdfMarkdownNodeParams",
|
13
11
|
]
|
@@ -4,75 +4,111 @@ import re
|
|
4
4
|
from typing import Optional
|
5
5
|
|
6
6
|
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
-
from notionary.blocks.file.file_element_models import
|
7
|
+
from notionary.blocks.file.file_element_models import (
|
8
|
+
ExternalFile,
|
9
|
+
FileBlock,
|
10
|
+
FileType,
|
11
|
+
FileUploadFile,
|
12
|
+
)
|
8
13
|
from notionary.blocks.mixins.captions import CaptionMixin
|
14
|
+
from notionary.blocks.mixins.file_upload.file_upload_mixin import FileUploadMixin
|
9
15
|
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
10
16
|
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
11
17
|
from notionary.blocks.pdf.pdf_models import CreatePdfBlock
|
12
18
|
|
13
19
|
|
14
|
-
class PdfElement(BaseBlockElement, CaptionMixin):
|
15
|
-
"""
|
20
|
+
class PdfElement(BaseBlockElement, CaptionMixin, FileUploadMixin):
|
21
|
+
r"""
|
16
22
|
Handles conversion between Markdown PDF embeds and Notion PDF blocks.
|
17
23
|
|
24
|
+
Supports both external URLs and local PDF file uploads.
|
25
|
+
|
18
26
|
Markdown PDF syntax:
|
19
27
|
- [pdf](https://example.com/document.pdf) - External URL
|
20
|
-
- [pdf](
|
21
|
-
-
|
28
|
+
- [pdf](./local/document.pdf) - Local PDF file (will be uploaded)
|
29
|
+
- [pdf](C:\Documents\report.pdf) - Absolute local path (will be uploaded)
|
30
|
+
- [pdf](https://example.com/document.pdf)(caption:Annual Report 2024) - With caption
|
22
31
|
- [pdf](notion://file_id_here)(caption:Notion hosted file) - Notion hosted file
|
23
32
|
- [pdf](upload://upload_id_here)(caption:File upload) - File upload
|
24
|
-
|
25
|
-
Supports all three PDF types: external, notion-hosted, and file uploads.
|
26
33
|
"""
|
27
34
|
|
28
|
-
|
29
|
-
PDF_PATTERN = re.compile(r"\[pdf\]\(((?:https?://|notion://|upload://)[^\s\"]+)\)")
|
35
|
+
PDF_PATTERN = re.compile(r"\[pdf\]\(([^)]+)\)")
|
30
36
|
|
31
37
|
@classmethod
|
32
38
|
def match_notion(cls, block: Block) -> bool:
|
33
|
-
|
39
|
+
"""Match Notion PDF blocks."""
|
34
40
|
return block.type == BlockType.PDF and block.pdf
|
35
41
|
|
36
42
|
@classmethod
|
37
|
-
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
38
|
-
"""Convert markdown PDF link to Notion
|
39
|
-
|
40
|
-
|
41
|
-
if not
|
43
|
+
async def markdown_to_notion(cls, text: str) -> Optional[BlockCreateResult]:
|
44
|
+
"""Convert markdown PDF link to Notion PDF block."""
|
45
|
+
pdf_path = cls._extract_pdf_path(text.strip())
|
46
|
+
|
47
|
+
if not pdf_path:
|
42
48
|
return None
|
43
49
|
|
44
|
-
|
50
|
+
cls.logger.info(f"Processing PDF: {pdf_path}")
|
45
51
|
|
46
|
-
#
|
52
|
+
# Extract caption
|
47
53
|
caption_text = cls.extract_caption(text.strip())
|
48
54
|
caption_rich_text = cls.build_caption_rich_text(caption_text or "")
|
49
55
|
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
# Handle different types of PDF sources
|
57
|
+
if pdf_path.startswith(("notion://", "upload://")):
|
58
|
+
# Handle special Notion schemes (existing functionality)
|
59
|
+
cls.logger.info(f"Using special scheme: {pdf_path}")
|
60
|
+
pdf_block = FileBlock(
|
61
|
+
type=FileType.EXTERNAL,
|
62
|
+
external=ExternalFile(url=pdf_path),
|
63
|
+
caption=caption_rich_text,
|
64
|
+
)
|
65
|
+
|
66
|
+
elif cls._is_local_file_path(pdf_path):
|
67
|
+
# Handle local PDF file upload using mixin
|
68
|
+
cls.logger.debug(f"Detected local PDF file: {pdf_path}")
|
69
|
+
|
70
|
+
# Upload the local PDF file with PDF category validation
|
71
|
+
file_upload_id = await cls._upload_local_file(pdf_path, "pdf")
|
72
|
+
if not file_upload_id:
|
73
|
+
cls.logger.error(f"Failed to upload PDF: {pdf_path}")
|
74
|
+
return None
|
75
|
+
|
76
|
+
# Create FILE_UPLOAD block
|
77
|
+
pdf_block = FileBlock(
|
78
|
+
type=FileType.FILE_UPLOAD,
|
79
|
+
file_upload=FileUploadFile(id=file_upload_id),
|
80
|
+
caption=caption_rich_text,
|
81
|
+
)
|
82
|
+
|
83
|
+
else:
|
84
|
+
# Handle external URL
|
85
|
+
cls.logger.debug(f"Using external PDF URL: {pdf_path}")
|
86
|
+
|
87
|
+
pdf_block = FileBlock(
|
88
|
+
type=FileType.EXTERNAL,
|
89
|
+
external=ExternalFile(url=pdf_path),
|
90
|
+
caption=caption_rich_text,
|
91
|
+
)
|
56
92
|
|
57
93
|
return CreatePdfBlock(pdf=pdf_block)
|
58
94
|
|
59
95
|
@classmethod
|
60
96
|
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
97
|
+
"""Convert Notion PDF block to markdown."""
|
61
98
|
if block.type != BlockType.PDF or not block.pdf:
|
62
99
|
return None
|
63
100
|
|
64
101
|
pb: FileBlock = block.pdf
|
65
102
|
|
103
|
+
# Determine the source for markdown
|
66
104
|
if pb.type == FileType.EXTERNAL and pb.external:
|
67
|
-
|
105
|
+
source = pb.external.url
|
68
106
|
elif pb.type == FileType.FILE and pb.file:
|
69
|
-
|
70
|
-
elif pb.type == FileType.FILE_UPLOAD:
|
71
|
-
return None
|
107
|
+
source = pb.file.url
|
72
108
|
else:
|
73
109
|
return None
|
74
110
|
|
75
|
-
result = f"[pdf]({
|
111
|
+
result = f"[pdf]({source})"
|
76
112
|
|
77
113
|
# Add caption if present
|
78
114
|
caption_markdown = await cls.format_caption_for_markdown(pb.caption or [])
|
@@ -86,12 +122,25 @@ class PdfElement(BaseBlockElement, CaptionMixin):
|
|
86
122
|
"""Get system prompt information for PDF blocks."""
|
87
123
|
return BlockElementMarkdownInformation(
|
88
124
|
block_type=cls.__name__,
|
89
|
-
description="PDF blocks embed and display PDF documents from external URLs with optional captions",
|
125
|
+
description="PDF blocks embed and display PDF documents from external URLs or upload local PDF files with optional captions",
|
90
126
|
syntax_examples=[
|
91
127
|
"[pdf](https://example.com/document.pdf)",
|
128
|
+
"[pdf](./local/report.pdf)",
|
129
|
+
"[pdf](C:\\Documents\\manual.pdf)",
|
92
130
|
"[pdf](https://example.com/report.pdf)(caption:Annual Report 2024)",
|
93
|
-
"(caption:User Manual)[pdf](
|
94
|
-
"[pdf](
|
131
|
+
"(caption:User Manual)[pdf](./manual.pdf)",
|
132
|
+
"[pdf](./guide.pdf)(caption:**Important** documentation)",
|
95
133
|
],
|
96
|
-
usage_guidelines="Use for embedding PDF documents that can be viewed inline. Supports external URLs and
|
134
|
+
usage_guidelines="Use for embedding PDF documents that can be viewed inline. Supports both external URLs and local PDF file uploads. Local PDF files will be automatically uploaded to Notion. Caption supports rich text formatting and should describe the PDF content.",
|
97
135
|
)
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def _extract_pdf_path(cls, text: str) -> Optional[str]:
|
139
|
+
"""Extract PDF path/URL from text, handling caption patterns."""
|
140
|
+
clean_text = cls.remove_caption(text)
|
141
|
+
|
142
|
+
match = cls.PDF_PATTERN.search(clean_text)
|
143
|
+
if match:
|
144
|
+
return match.group(1).strip()
|
145
|
+
|
146
|
+
return None
|
@@ -1,30 +1,17 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
from typing import Optional
|
4
2
|
|
5
|
-
from
|
6
|
-
|
7
|
-
from notionary.markdown.markdown_node import MarkdownNode
|
3
|
+
from notionary.blocks.markdown.markdown_node import MarkdownNode
|
8
4
|
from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
|
9
5
|
|
10
6
|
|
11
|
-
class PdfMarkdownNodeParams(BaseModel):
|
12
|
-
url: str
|
13
|
-
caption: Optional[str] = None
|
14
|
-
|
15
|
-
|
16
7
|
class PdfMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
|
17
8
|
"""
|
18
|
-
|
9
|
+
Enhanced PDF node with Pydantic integration.
|
10
|
+
Programmatic interface for creating Notion-style PDF blocks.
|
19
11
|
"""
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
self.caption = caption or ""
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
def from_params(cls, params: PdfMarkdownNodeParams) -> PdfMarkdownNode:
|
27
|
-
return cls(url=params.url, caption=params.caption)
|
13
|
+
url: str
|
14
|
+
caption: Optional[str] = None
|
28
15
|
|
29
16
|
def to_markdown(self) -> str:
|
30
17
|
"""Return the Markdown representation.
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
from notionary.blocks.quote.quote_element import QuoteElement
|
4
4
|
from notionary.blocks.quote.quote_markdown_node import (
|
5
|
-
QuoteMarkdownBlockParams,
|
6
5
|
QuoteMarkdownNode,
|
7
6
|
)
|
8
7
|
from notionary.blocks.quote.quote_models import CreateQuoteBlock, QuoteBlock
|
@@ -12,5 +11,4 @@ __all__ = [
|
|
12
11
|
"QuoteBlock",
|
13
12
|
"CreateQuoteBlock",
|
14
13
|
"QuoteMarkdownNode",
|
15
|
-
"QuoteMarkdownBlockParams",
|
16
14
|
]
|
@@ -1,26 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
-
|
7
|
-
|
8
|
-
class QuoteMarkdownBlockParams(BaseModel):
|
9
|
-
text: str
|
3
|
+
from notionary.blocks.markdown.markdown_node import MarkdownNode
|
10
4
|
|
11
5
|
|
12
6
|
class QuoteMarkdownNode(MarkdownNode):
|
13
7
|
"""
|
8
|
+
Enhanced Quote node with Pydantic integration.
|
14
9
|
Programmatic interface for creating Notion-style quote blocks.
|
15
10
|
Example: > This is a quote
|
16
11
|
"""
|
17
12
|
|
18
|
-
|
19
|
-
self.text = text
|
20
|
-
|
21
|
-
@classmethod
|
22
|
-
def from_params(cls, params: QuoteMarkdownBlockParams) -> QuoteMarkdownNode:
|
23
|
-
return cls(text=params.text)
|
13
|
+
text: str
|
24
14
|
|
25
15
|
def to_markdown(self) -> str:
|
26
16
|
return f"> {self.text}"
|
@@ -1,95 +1,150 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
3
|
+
from collections import OrderedDict
|
4
|
+
from typing import Type, Set
|
4
5
|
|
5
6
|
from notionary.blocks.base_block_element import BaseBlockElement
|
6
|
-
from notionary.
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
from notionary.telemetry import ProductTelemetry
|
8
|
+
|
9
|
+
from notionary.blocks.audio import AudioElement
|
10
|
+
from notionary.blocks.bookmark import BookmarkElement
|
11
|
+
from notionary.blocks.breadcrumbs import BreadcrumbElement
|
12
|
+
from notionary.blocks.bulleted_list import BulletedListElement
|
13
|
+
from notionary.blocks.callout import CalloutElement
|
14
|
+
from notionary.blocks.child_database import ChildDatabaseElement
|
15
|
+
from notionary.blocks.code import CodeElement
|
16
|
+
from notionary.blocks.column import ColumnElement, ColumnListElement
|
17
|
+
from notionary.blocks.divider import DividerElement
|
18
|
+
from notionary.blocks.embed import EmbedElement
|
19
|
+
from notionary.blocks.equation import EquationElement
|
20
|
+
from notionary.blocks.file import FileElement
|
21
|
+
from notionary.blocks.heading import HeadingElement
|
22
|
+
from notionary.blocks.image_block import ImageElement
|
23
|
+
from notionary.blocks.numbered_list import NumberedListElement
|
24
|
+
from notionary.blocks.paragraph import ParagraphElement
|
25
|
+
from notionary.blocks.pdf import PdfElement
|
26
|
+
from notionary.blocks.quote import QuoteElement
|
27
|
+
from notionary.blocks.table import TableElement
|
28
|
+
from notionary.blocks.table_of_contents import TableOfContentsElement
|
29
|
+
from notionary.blocks.todo import TodoElement
|
30
|
+
from notionary.blocks.toggle import ToggleElement
|
31
|
+
from notionary.blocks.toggleable_heading import ToggleableHeadingElement
|
32
|
+
from notionary.blocks.video import VideoElement
|
10
33
|
|
11
34
|
|
12
35
|
class BlockRegistry:
|
13
36
|
"""Registry of elements that can convert between Markdown and Notion."""
|
14
37
|
|
15
|
-
|
38
|
+
_DEFAULT_ELEMENTS = [
|
39
|
+
HeadingElement,
|
40
|
+
CalloutElement,
|
41
|
+
CodeElement,
|
42
|
+
DividerElement,
|
43
|
+
TableElement,
|
44
|
+
BulletedListElement,
|
45
|
+
NumberedListElement,
|
46
|
+
ToggleElement,
|
47
|
+
ToggleableHeadingElement,
|
48
|
+
QuoteElement,
|
49
|
+
TodoElement,
|
50
|
+
BookmarkElement,
|
51
|
+
ImageElement,
|
52
|
+
VideoElement,
|
53
|
+
EmbedElement,
|
54
|
+
AudioElement,
|
55
|
+
ColumnListElement,
|
56
|
+
ColumnElement,
|
57
|
+
EquationElement,
|
58
|
+
TableOfContentsElement,
|
59
|
+
BreadcrumbElement,
|
60
|
+
ChildDatabaseElement,
|
61
|
+
FileElement,
|
62
|
+
PdfElement,
|
63
|
+
ParagraphElement, # Must be last as fallback!
|
64
|
+
]
|
65
|
+
|
66
|
+
def __init__(self, excluded_elements: Set[Type[BaseBlockElement]] = None):
|
16
67
|
"""
|
17
68
|
Initialize a new registry instance.
|
18
69
|
|
19
70
|
Args:
|
20
|
-
|
71
|
+
excluded_elements: Set of element classes to exclude from the registry
|
21
72
|
"""
|
22
|
-
|
23
|
-
|
24
|
-
BlockRegistryBuilder,
|
25
|
-
)
|
26
|
-
|
27
|
-
self._builder: BlockRegistryBuilder = builder or BlockRegistryBuilder()
|
73
|
+
self._elements = OrderedDict()
|
74
|
+
self._excluded_elements = excluded_elements or set()
|
28
75
|
self.telemetry = ProductTelemetry()
|
29
76
|
|
77
|
+
# Initialize with default elements minus excluded ones
|
78
|
+
self._initialize_default_elements()
|
79
|
+
|
30
80
|
@classmethod
|
31
|
-
def create_registry(
|
81
|
+
def create_registry(
|
82
|
+
cls, excluded_elements: Set[Type[BaseBlockElement]] = None
|
83
|
+
) -> "BlockRegistry":
|
32
84
|
"""
|
33
85
|
Create a registry with all standard elements in recommended order.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
excluded_elements: Set of element classes to exclude from the registry
|
89
|
+
"""
|
90
|
+
return cls(excluded_elements=excluded_elements)
|
91
|
+
|
92
|
+
def _initialize_default_elements(self) -> None:
|
93
|
+
"""Initialize registry with default elements minus excluded ones."""
|
94
|
+
for element_class in self._DEFAULT_ELEMENTS:
|
95
|
+
if element_class not in self._excluded_elements:
|
96
|
+
self._elements[element_class.__name__] = element_class
|
97
|
+
|
98
|
+
def exclude_elements(
|
99
|
+
self, *element_classes: Type[BaseBlockElement]
|
100
|
+
) -> BlockRegistry:
|
101
|
+
"""
|
102
|
+
Create a new registry with additional excluded elements.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
element_classes: Element classes to exclude
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
New BlockRegistry instance with excluded elements
|
34
109
|
"""
|
35
|
-
|
36
|
-
|
37
|
-
)
|
38
|
-
|
39
|
-
builder = BlockRegistryBuilder()
|
40
|
-
builder = (
|
41
|
-
builder.with_headings()
|
42
|
-
.with_callouts()
|
43
|
-
.with_code()
|
44
|
-
.with_dividers()
|
45
|
-
.with_tables()
|
46
|
-
.with_bulleted_list()
|
47
|
-
.with_numbered_list()
|
48
|
-
.with_toggles()
|
49
|
-
.with_toggleable_heading_element()
|
50
|
-
.with_quotes()
|
51
|
-
.with_todos()
|
52
|
-
.with_bookmarks()
|
53
|
-
.with_images()
|
54
|
-
.with_videos()
|
55
|
-
.with_embeds()
|
56
|
-
.with_audio()
|
57
|
-
.with_columns()
|
58
|
-
.with_equation()
|
59
|
-
.with_table_of_contents()
|
60
|
-
.with_breadcrumbs()
|
61
|
-
.with_child_database()
|
62
|
-
.with_paragraphs() # position here is important - its a fallback!
|
63
|
-
)
|
64
|
-
|
65
|
-
return cls(builder=builder)
|
66
|
-
|
67
|
-
@property
|
68
|
-
def builder(self) -> BlockRegistryBuilder:
|
69
|
-
return self._builder
|
110
|
+
new_excluded = self._excluded_elements.copy()
|
111
|
+
new_excluded.update(element_classes)
|
112
|
+
return BlockRegistry(excluded_elements=new_excluded)
|
70
113
|
|
71
114
|
def register(self, element_class: Type[BaseBlockElement]) -> bool:
|
72
115
|
"""
|
73
|
-
Register an element class
|
116
|
+
Register an element class.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
element_class: The element class to register
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
True if element was added, False if it already existed
|
74
123
|
"""
|
75
|
-
|
76
|
-
|
77
|
-
|
124
|
+
if element_class.__name__ in self._elements:
|
125
|
+
return False
|
126
|
+
|
127
|
+
self._elements[element_class.__name__] = element_class
|
128
|
+
return True
|
78
129
|
|
79
|
-
def
|
130
|
+
def remove(self, element_class: Type[BaseBlockElement]) -> bool:
|
80
131
|
"""
|
81
|
-
|
132
|
+
Remove an element class.
|
82
133
|
"""
|
83
|
-
|
84
|
-
self._builder.remove_element(element_class)
|
85
|
-
return len(self._builder._elements) < initial_count
|
134
|
+
return self._elements.pop(element_class.__name__, None) is not None
|
86
135
|
|
87
136
|
def contains(self, element_class: Type[BaseBlockElement]) -> bool:
|
88
137
|
"""
|
89
138
|
Checks if a specific element is contained in the registry.
|
90
139
|
"""
|
91
|
-
return element_class.__name__ in self.
|
140
|
+
return element_class.__name__ in self._elements
|
92
141
|
|
93
142
|
def get_elements(self) -> list[Type[BaseBlockElement]]:
|
94
|
-
"""Get all registered elements."""
|
95
|
-
return list(self.
|
143
|
+
"""Get all registered elements in order."""
|
144
|
+
return list(self._elements.values())
|
145
|
+
|
146
|
+
def is_excluded(self, element_class: Type[BaseBlockElement]) -> bool:
|
147
|
+
"""
|
148
|
+
Check if an element class is excluded.
|
149
|
+
"""
|
150
|
+
return element_class in self._excluded_elements
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from notionary.blocks.table.table_element import TableElement
|
2
2
|
from notionary.blocks.table.table_markdown_node import (
|
3
|
-
TableMarkdownBlockParams,
|
4
3
|
TableMarkdownNode,
|
5
4
|
)
|
6
5
|
from notionary.blocks.table.table_models import (
|
@@ -17,5 +16,4 @@ __all__ = [
|
|
17
16
|
"CreateTableRowBlock",
|
18
17
|
"CreateTableBlock",
|
19
18
|
"TableMarkdownNode",
|
20
|
-
"TableMarkdownBlockParams",
|
21
19
|
]
|
@@ -1,17 +1,11 @@
|
|
1
|
-
from
|
1
|
+
from pydantic import field_validator
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
-
|
7
|
-
|
8
|
-
class TableMarkdownBlockParams(BaseModel):
|
9
|
-
headers: list[str]
|
10
|
-
rows: list[list[str]]
|
3
|
+
from notionary.blocks.markdown.markdown_node import MarkdownNode
|
11
4
|
|
12
5
|
|
13
6
|
class TableMarkdownNode(MarkdownNode):
|
14
7
|
"""
|
8
|
+
Enhanced Table node with Pydantic integration.
|
15
9
|
Programmatic interface for creating Markdown tables.
|
16
10
|
Example:
|
17
11
|
| Header 1 | Header 2 | Header 3 |
|
@@ -20,15 +14,22 @@ class TableMarkdownNode(MarkdownNode):
|
|
20
14
|
| Cell 4 | Cell 5 | Cell 6 |
|
21
15
|
"""
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
headers: list[str]
|
18
|
+
rows: list[list[str]]
|
19
|
+
|
20
|
+
@field_validator("headers")
|
21
|
+
@classmethod
|
22
|
+
def validate_headers(cls, v):
|
23
|
+
if not v:
|
24
|
+
raise ValueError("headers must not be empty")
|
25
|
+
return v
|
28
26
|
|
27
|
+
@field_validator("rows")
|
29
28
|
@classmethod
|
30
|
-
def
|
31
|
-
|
29
|
+
def validate_rows(cls, v):
|
30
|
+
if not all(isinstance(row, list) for row in v):
|
31
|
+
raise ValueError("rows must be a list of lists")
|
32
|
+
return v
|
32
33
|
|
33
34
|
def to_markdown(self) -> str:
|
34
35
|
col_count = len(self.headers)
|
@@ -2,7 +2,6 @@ from notionary.blocks.table_of_contents.table_of_contents_element import (
|
|
2
2
|
TableOfContentsElement,
|
3
3
|
)
|
4
4
|
from notionary.blocks.table_of_contents.table_of_contents_markdown_node import (
|
5
|
-
TableOfContentsMarkdownBlockParams,
|
6
5
|
TableOfContentsMarkdownNode,
|
7
6
|
)
|
8
7
|
from notionary.blocks.table_of_contents.table_of_contents_models import (
|
@@ -15,5 +14,4 @@ __all__ = [
|
|
15
14
|
"TableOfContentsBlock",
|
16
15
|
"CreateTableOfContentsBlock",
|
17
16
|
"TableOfContentsMarkdownNode",
|
18
|
-
"TableOfContentsMarkdownBlockParams",
|
19
17
|
]
|