notionary 0.2.22__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.
Files changed (105) hide show
  1. notionary/__init__.py +1 -1
  2. notionary/blocks/__init__.py +3 -1
  3. notionary/blocks/audio/__init__.py +0 -2
  4. notionary/blocks/audio/audio_element.py +92 -49
  5. notionary/blocks/audio/audio_markdown_node.py +4 -17
  6. notionary/blocks/bookmark/__init__.py +0 -2
  7. notionary/blocks/bookmark/bookmark_markdown_node.py +5 -21
  8. notionary/blocks/breadcrumbs/__init__.py +0 -2
  9. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +2 -21
  10. notionary/blocks/bulleted_list/__init__.py +0 -2
  11. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +3 -17
  12. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -1
  13. notionary/blocks/callout/__init__.py +0 -2
  14. notionary/blocks/callout/callout_markdown_node.py +4 -18
  15. notionary/blocks/callout/callout_models.py +3 -4
  16. notionary/blocks/child_database/child_database_element.py +2 -4
  17. notionary/blocks/code/code_markdown_node.py +5 -19
  18. notionary/blocks/column/__init__.py +0 -4
  19. notionary/blocks/column/column_list_markdown_node.py +3 -19
  20. notionary/blocks/column/column_markdown_node.py +4 -21
  21. notionary/blocks/divider/__init__.py +0 -2
  22. notionary/blocks/divider/divider_markdown_node.py +2 -16
  23. notionary/blocks/embed/__init__.py +0 -2
  24. notionary/blocks/embed/embed_markdown_node.py +4 -17
  25. notionary/blocks/equation/__init__.py +0 -1
  26. notionary/blocks/equation/equation_element_markdown_node.py +3 -15
  27. notionary/blocks/file/__init__.py +0 -2
  28. notionary/blocks/file/file_element.py +67 -46
  29. notionary/blocks/file/file_element_markdown_node.py +4 -17
  30. notionary/blocks/heading/__init__.py +0 -2
  31. notionary/blocks/heading/heading_markdown_node.py +5 -19
  32. notionary/blocks/heading/heading_models.py +3 -3
  33. notionary/blocks/image_block/__init__.py +0 -2
  34. notionary/blocks/image_block/image_element.py +66 -25
  35. notionary/blocks/image_block/image_markdown_node.py +5 -20
  36. notionary/{markdown → blocks/markdown}/markdown_builder.py +29 -233
  37. notionary/blocks/markdown/markdown_node.py +25 -0
  38. notionary/blocks/mixins/file_upload/__init__.py +3 -0
  39. notionary/blocks/mixins/file_upload/file_upload_mixin.py +320 -0
  40. notionary/blocks/numbered_list/__init__.py +0 -1
  41. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -17
  42. notionary/blocks/numbered_list/numbered_list_models.py +3 -3
  43. notionary/blocks/paragraph/__init__.py +0 -2
  44. notionary/blocks/paragraph/paragraph_markdown_node.py +3 -13
  45. notionary/blocks/pdf/__init__.py +0 -2
  46. notionary/blocks/pdf/pdf_element.py +81 -32
  47. notionary/blocks/pdf/pdf_markdown_node.py +5 -18
  48. notionary/blocks/quote/__init__.py +0 -2
  49. notionary/blocks/quote/quote_markdown_node.py +3 -13
  50. notionary/blocks/registry/__init__.py +1 -2
  51. notionary/blocks/registry/block_registry.py +116 -61
  52. notionary/blocks/rich_text/text_inline_formatter.py +1 -1
  53. notionary/blocks/table/__init__.py +0 -2
  54. notionary/blocks/table/table_markdown_node.py +17 -16
  55. notionary/blocks/table_of_contents/__init__.py +0 -2
  56. notionary/blocks/table_of_contents/table_of_contents_element.py +27 -15
  57. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +3 -17
  58. notionary/blocks/table_of_contents/table_of_contents_models.py +2 -2
  59. notionary/blocks/todo/__init__.py +0 -2
  60. notionary/blocks/todo/todo_markdown_node.py +9 -20
  61. notionary/blocks/todo/todo_models.py +2 -3
  62. notionary/blocks/toggle/__init__.py +0 -2
  63. notionary/blocks/toggle/toggle_markdown_node.py +5 -19
  64. notionary/blocks/toggleable_heading/__init__.py +0 -2
  65. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +6 -23
  66. notionary/blocks/video/__init__.py +0 -2
  67. notionary/blocks/video/video_element.py +110 -34
  68. notionary/blocks/video/video_markdown_node.py +4 -15
  69. notionary/comments/__init__.py +26 -0
  70. notionary/comments/client.py +211 -0
  71. notionary/comments/models.py +129 -0
  72. notionary/file_upload/client.py +3 -2
  73. notionary/file_upload/models.py +10 -1
  74. notionary/file_upload/notion_file_upload.py +5 -5
  75. notionary/page/client.py +1 -6
  76. notionary/page/markdown_whitespace_processor.py +129 -0
  77. notionary/page/notion_page.py +87 -48
  78. notionary/page/page_content_deleting_service.py +1 -1
  79. notionary/page/page_content_writer.py +32 -129
  80. notionary/page/page_context.py +0 -6
  81. notionary/page/reader/handler/column_list_renderer.py +2 -2
  82. notionary/page/reader/handler/column_renderer.py +2 -2
  83. notionary/page/reader/handler/line_renderer.py +2 -2
  84. notionary/page/reader/handler/toggle_renderer.py +2 -2
  85. notionary/page/reader/handler/toggleable_heading_renderer.py +2 -2
  86. notionary/page/writer/handler/toggle_handler.py +8 -4
  87. notionary/page/writer/handler/toggleable_heading_handler.py +3 -2
  88. notionary/page/writer/markdown_to_notion_converter.py +74 -30
  89. notionary/schemas/__init__.py +3 -0
  90. notionary/schemas/base.py +73 -0
  91. notionary/shared/__init__.py +3 -0
  92. notionary/{blocks/rich_text → shared}/name_to_id_resolver.py +0 -2
  93. {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/METADATA +15 -2
  94. {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/RECORD +97 -95
  95. notionary/blocks/guards.py +0 -22
  96. notionary/blocks/registry/block_registry_builder.py +0 -264
  97. notionary/markdown/makdown_document_model.py +0 -0
  98. notionary/markdown/markdown_document_model.py +0 -228
  99. notionary/markdown/markdown_node.py +0 -30
  100. notionary/models/notion_database_response.py +0 -0
  101. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -73
  102. notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
  103. /notionary/{markdown/___init__.py → blocks/markdown/markdown_document_model.py} +0 -0
  104. {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/LICENSE +0 -0
  105. {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field
2
- from typing_extensions import Literal
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] = Field(default_factory=list)
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 pydantic import BaseModel
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
- def __init__(self, text: str):
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
@@ -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 ExternalFile, FileBlock, FileType
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](https://example.com/document.pdf)(caption:Annual Report 2024) - URL with caption
21
- - (caption:User Manual)[pdf](https://example.com/manual.pdf) - caption before URL
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
- # Flexible pattern that can handle caption in any position
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
- # Notion PDF block covers PDFs
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 FileBlock (used for PDF)."""
39
- # Use our own regex to find the PDF URL
40
- pdf_match = cls.PDF_PATTERN.search(text.strip())
41
- if not pdf_match:
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
- url = pdf_match.group(1)
50
+ cls.logger.info(f"Processing PDF: {pdf_path}")
45
51
 
46
- # Use mixin to extract caption (if present anywhere in text)
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
- # Build FileBlock using FileType enum (reused for PDF)
51
- pdf_block = FileBlock(
52
- type=FileType.EXTERNAL,
53
- external=ExternalFile(url=url),
54
- caption=caption_rich_text,
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
- url = pb.external.url
105
+ source = pb.external.url
68
106
  elif pb.type == FileType.FILE and pb.file:
69
- url = pb.file.url
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]({url})"
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](https://example.com/manual.pdf)",
94
- "[pdf](https://example.com/guide.pdf)(caption:**Important** documentation)",
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 Notion-hosted files. Caption supports rich text formatting and should describe the PDF content.",
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 pydantic import BaseModel
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
- Programmatic interface for creating Notion-style Markdown PDF embeds.
9
+ Enhanced PDF node with Pydantic integration.
10
+ Programmatic interface for creating Notion-style PDF blocks.
19
11
  """
20
12
 
21
- def __init__(self, url: str, caption: Optional[str] = None):
22
- self.url = url
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 pydantic import BaseModel
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
- def __init__(self, text: str):
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,4 +1,3 @@
1
1
  from .block_registry import BlockRegistry
2
- from .block_registry_builder import BlockRegistryBuilder
3
2
 
4
- __all__ = ["BlockRegistryBuilder", "BlockRegistry"]
3
+ __all__ = ["BlockRegistry"]
@@ -1,95 +1,150 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional, Type
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.blocks.registry.block_registry_builder import BlockRegistryBuilder
7
- from notionary.telemetry import (
8
- ProductTelemetry,
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
- def __init__(self, builder: Optional[BlockRegistryBuilder] = None):
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
- builder: BlockRegistryBuilder instance to delegate operations to
71
+ excluded_elements: Set of element classes to exclude from the registry
21
72
  """
22
- # Import here to avoid circular imports
23
- from notionary.blocks.registry.block_registry_builder import (
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(cls) -> BlockRegistry:
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
- from notionary.blocks.registry.block_registry_builder import (
36
- BlockRegistryBuilder,
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 via builder.
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
- initial_count = len(self._builder._elements)
76
- self._builder._add_element(element_class)
77
- return len(self._builder._elements) > initial_count
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 deregister(self, element_class: Type[BaseBlockElement]) -> bool:
130
+ def remove(self, element_class: Type[BaseBlockElement]) -> bool:
80
131
  """
81
- Deregister an element class via builder.
132
+ Remove an element class.
82
133
  """
83
- initial_count = len(self._builder._elements)
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._builder._elements
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._builder._elements.values())
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
@@ -10,7 +10,7 @@ from notionary.blocks.rich_text.rich_text_models import (
10
10
  MentionTemplateMention,
11
11
  )
12
12
  from notionary.blocks.types import BlockColor
13
- from notionary.blocks.rich_text.name_to_id_resolver import NameIdResolver
13
+ from notionary.shared import NameIdResolver
14
14
 
15
15
 
16
16
  class TextInlineFormatter:
@@ -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 __future__ import annotations
1
+ from pydantic import field_validator
2
2
 
3
- from pydantic import BaseModel
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
- def __init__(self, headers: list[str], rows: list[list[str]]):
24
- if not headers or not all(isinstance(row, list) for row in rows):
25
- raise ValueError("headers must be a list and rows must be a list of lists")
26
- self.headers = headers
27
- self.rows = rows
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 from_params(cls, params: TableMarkdownBlockParams) -> TableMarkdownNode:
31
- return cls(headers=params.headers, rows=params.rows)
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
  ]