notionary 0.2.17__py3-none-any.whl → 0.2.19__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 (113) hide show
  1. notionary/__init__.py +3 -2
  2. notionary/blocks/__init__.py +54 -25
  3. notionary/blocks/audio/__init__.py +7 -0
  4. notionary/blocks/audio/audio_element.py +152 -0
  5. notionary/blocks/audio/audio_markdown_node.py +29 -0
  6. notionary/blocks/audio/audio_models.py +59 -0
  7. notionary/blocks/bookmark/__init__.py +7 -0
  8. notionary/blocks/{bookmark_element.py → bookmark/bookmark_element.py} +20 -65
  9. notionary/blocks/bookmark/bookmark_markdown_node.py +43 -0
  10. notionary/blocks/bookmark/bookmark_models.py +0 -0
  11. notionary/blocks/bulleted_list/__init__.py +7 -0
  12. notionary/blocks/{bulleted_list_element.py → bulleted_list/bulleted_list_element.py} +7 -3
  13. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +33 -0
  14. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -0
  15. notionary/blocks/callout/__init__.py +7 -0
  16. notionary/blocks/callout/callout_element.py +132 -0
  17. notionary/blocks/callout/callout_markdown_node.py +31 -0
  18. notionary/blocks/callout/callout_models.py +0 -0
  19. notionary/blocks/code/__init__.py +7 -0
  20. notionary/blocks/{code_block_element.py → code/code_element.py} +72 -40
  21. notionary/blocks/code/code_markdown_node.py +43 -0
  22. notionary/blocks/code/code_models.py +0 -0
  23. notionary/blocks/column/__init__.py +5 -0
  24. notionary/blocks/{column_element.py → column/column_element.py} +24 -55
  25. notionary/blocks/column/column_models.py +0 -0
  26. notionary/blocks/divider/__init__.py +7 -0
  27. notionary/blocks/{divider_element.py → divider/divider_element.py} +11 -3
  28. notionary/blocks/divider/divider_markdown_node.py +24 -0
  29. notionary/blocks/divider/divider_models.py +0 -0
  30. notionary/blocks/document/__init__.py +7 -0
  31. notionary/blocks/document/document_element.py +102 -0
  32. notionary/blocks/document/document_markdown_node.py +31 -0
  33. notionary/blocks/document/document_models.py +0 -0
  34. notionary/blocks/embed/__init__.py +7 -0
  35. notionary/blocks/{embed_element.py → embed/embed_element.py} +50 -32
  36. notionary/blocks/embed/embed_markdown_node.py +30 -0
  37. notionary/blocks/embed/embed_models.py +0 -0
  38. notionary/blocks/heading/__init__.py +7 -0
  39. notionary/blocks/{heading_element.py → heading/heading_element.py} +25 -17
  40. notionary/blocks/heading/heading_markdown_node.py +29 -0
  41. notionary/blocks/heading/heading_models.py +0 -0
  42. notionary/blocks/image/__init__.py +7 -0
  43. notionary/blocks/{image_element.py → image/image_element.py} +62 -42
  44. notionary/blocks/image/image_markdown_node.py +33 -0
  45. notionary/blocks/image/image_models.py +0 -0
  46. notionary/blocks/markdown_builder.py +356 -0
  47. notionary/blocks/markdown_node.py +29 -0
  48. notionary/blocks/mention/__init__.py +7 -0
  49. notionary/blocks/{mention_element.py → mention/mention_element.py} +6 -2
  50. notionary/blocks/mention/mention_markdown_node.py +38 -0
  51. notionary/blocks/mention/mention_models.py +0 -0
  52. notionary/blocks/numbered_list/__init__.py +7 -0
  53. notionary/blocks/{numbered_list_element.py → numbered_list/numbered_list_element.py} +10 -6
  54. notionary/blocks/numbered_list/numbered_list_markdown_node.py +29 -0
  55. notionary/blocks/numbered_list/numbered_list_models.py +0 -0
  56. notionary/blocks/paragraph/__init__.py +7 -0
  57. notionary/blocks/{paragraph_element.py → paragraph/paragraph_element.py} +7 -3
  58. notionary/blocks/paragraph/paragraph_markdown_node.py +25 -0
  59. notionary/blocks/paragraph/paragraph_models.py +0 -0
  60. notionary/blocks/quote/__init__.py +7 -0
  61. notionary/blocks/quote/quote_element.py +92 -0
  62. notionary/blocks/quote/quote_markdown_node.py +23 -0
  63. notionary/blocks/quote/quote_models.py +0 -0
  64. notionary/blocks/registry/block_registry.py +17 -3
  65. notionary/blocks/registry/block_registry_builder.py +90 -178
  66. notionary/blocks/shared/__init__.py +0 -0
  67. notionary/blocks/shared/block_client.py +256 -0
  68. notionary/blocks/shared/models.py +713 -0
  69. notionary/blocks/{notion_block_element.py → shared/notion_block_element.py} +8 -5
  70. notionary/blocks/{text_inline_formatter.py → shared/text_inline_formatter.py} +14 -14
  71. notionary/blocks/shared/text_inline_formatter_new.py +139 -0
  72. notionary/blocks/table/__init__.py +7 -0
  73. notionary/blocks/{table_element.py → table/table_element.py} +23 -11
  74. notionary/blocks/table/table_markdown_node.py +40 -0
  75. notionary/blocks/table/table_models.py +0 -0
  76. notionary/blocks/todo/__init__.py +7 -0
  77. notionary/blocks/{todo_element.py → todo/todo_element.py} +8 -4
  78. notionary/blocks/todo/todo_markdown_node.py +31 -0
  79. notionary/blocks/todo/todo_models.py +0 -0
  80. notionary/blocks/toggle/__init__.py +4 -0
  81. notionary/blocks/{toggle_element.py → toggle/toggle_element.py} +7 -3
  82. notionary/blocks/toggle/toggle_markdown_node.py +35 -0
  83. notionary/blocks/toggle/toggle_models.py +0 -0
  84. notionary/blocks/toggleable_heading/__init__.py +9 -0
  85. notionary/blocks/{toggleable_heading_element.py → toggleable_heading/toggleable_heading_element.py} +8 -4
  86. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +43 -0
  87. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  88. notionary/blocks/video/__init__.py +7 -0
  89. notionary/blocks/{video_element.py → video/video_element.py} +82 -57
  90. notionary/blocks/video/video_markdown_node.py +30 -0
  91. notionary/file_upload/notion_file_upload.py +1 -1
  92. notionary/page/content/markdown_whitespace_processor.py +80 -0
  93. notionary/page/content/notion_text_length_utils.py +87 -0
  94. notionary/page/content/page_content_retriever.py +18 -10
  95. notionary/page/content/page_content_writer.py +97 -148
  96. notionary/page/formatting/line_processor.py +153 -0
  97. notionary/page/formatting/markdown_to_notion_converter.py +104 -425
  98. notionary/page/notion_page.py +9 -11
  99. notionary/page/notion_to_markdown_converter.py +9 -13
  100. notionary/util/factory_decorator.py +0 -0
  101. notionary/workspace.py +0 -1
  102. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/METADATA +1 -1
  103. notionary-0.2.19.dist-info/RECORD +150 -0
  104. notionary/blocks/audio_element.py +0 -144
  105. notionary/blocks/callout_element.py +0 -122
  106. notionary/blocks/document_element.py +0 -194
  107. notionary/blocks/notion_block_client.py +0 -26
  108. notionary/blocks/qoute_element.py +0 -169
  109. notionary/page/content/notion_page_content_chunker.py +0 -84
  110. notionary/page/formatting/spacer_rules.py +0 -483
  111. notionary-0.2.17.dist-info/RECORD +0 -85
  112. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/LICENSE +0 -0
  113. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/WHEEL +0 -0
@@ -0,0 +1,92 @@
1
+ import re
2
+ from typing import Dict, Any, Optional, List, Tuple
3
+
4
+ from notionary.blocks import NotionBlockElement, NotionBlockResult
5
+ from notionary.blocks import ElementPromptContent, ElementPromptBuilder
6
+
7
+ class QuoteElement(NotionBlockElement):
8
+ """
9
+ Handles conversion between Markdown quotes and Notion quote blocks.
10
+ Markdown quote syntax:
11
+ - [quote](Simple quote text)
12
+ """
13
+
14
+ # Einzeilig, kein Author, kein Anführungszeichen-Kram mehr!
15
+ PATTERN = re.compile(r'^\[quote\]\(([^\n\r]+)\)$')
16
+
17
+ @classmethod
18
+ def match_markdown(cls, text: str) -> bool:
19
+ m = cls.PATTERN.match(text.strip())
20
+ # Nur gültig, wenn etwas nicht-leeres drinsteht
21
+ return bool(m and m.group(1).strip())
22
+
23
+ @classmethod
24
+ def match_notion(cls, block: Dict[str, Any]) -> bool:
25
+ return block.get("type") == "quote"
26
+
27
+ @classmethod
28
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
29
+ if not text:
30
+ return None
31
+
32
+ match = cls.PATTERN.match(text.strip())
33
+ if not match:
34
+ return None
35
+
36
+ content = match.group(1).strip()
37
+ if not content:
38
+ return None
39
+
40
+ rich_text = [{"type": "text", "text": {"content": content}}]
41
+ return {"type": "quote", "quote": {"rich_text": rich_text, "color": "default"}}
42
+
43
+ @classmethod
44
+ def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
45
+ if block.get("type") != "quote":
46
+ return None
47
+
48
+ rich_text = block.get("quote", {}).get("rich_text", [])
49
+ content = cls._extract_text_content(rich_text)
50
+ if not content.strip():
51
+ return None
52
+
53
+ return f"[quote]({content.strip()})"
54
+
55
+ @classmethod
56
+ def find_matches(cls, text: str) -> List[Tuple[int, int, Dict[str, Any]]]:
57
+ matches = []
58
+ for match in re.finditer(r"^\[quote\]\([^\n\r]+\)$", text, re.MULTILINE):
59
+ candidate = match.group(0)
60
+ block = cls.markdown_to_notion(candidate)
61
+ if block:
62
+ matches.append((match.start(), match.end(), block))
63
+ return matches
64
+
65
+ @classmethod
66
+ def is_multiline(cls) -> bool:
67
+ return False
68
+
69
+ @classmethod
70
+ def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
71
+ return "".join(
72
+ t.get("text", {}).get("content", "")
73
+ for t in rich_text
74
+ if t.get("type") == "text"
75
+ )
76
+
77
+ @classmethod
78
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
79
+ return (
80
+ ElementPromptBuilder()
81
+ .with_description("Creates blockquotes that visually distinguish quoted text.")
82
+ .with_usage_guidelines(
83
+ "Use quotes for quoting external sources, highlighting important statements, "
84
+ "or creating visual emphasis for key information."
85
+ )
86
+ .with_syntax('[quote](Quote text)')
87
+ .with_examples([
88
+ "[quote](This is a simple blockquote)",
89
+ "[quote](Knowledge is power)",
90
+ ])
91
+ .build()
92
+ )
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+ from notionary.blocks.markdown_node import MarkdownNode
5
+
6
+ class QuoteMarkdownBlockParams(BaseModel):
7
+ text: str
8
+
9
+ class QuoteMarkdownNode(MarkdownNode):
10
+ """
11
+ Programmatic interface for creating Notion-style quote blocks.
12
+ Example: [quote](This is a quote)
13
+ """
14
+
15
+ def __init__(self, text: str):
16
+ self.text = text
17
+
18
+ @classmethod
19
+ def from_params(cls, params: QuoteMarkdownBlockParams) -> QuoteMarkdownNode:
20
+ return cls(text=params.text)
21
+
22
+ def to_markdown(self) -> str:
23
+ return f"[quote]({self.text})"
File without changes
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
  from typing import Dict, Any, Optional, List, Set, Type
3
3
 
4
- from notionary.blocks.notion_block_element import NotionBlockElement
4
+ from notionary.blocks import NotionBlockElement
5
5
  from notionary.page.markdown_syntax_prompt_generator import (
6
6
  MarkdownSyntaxPromptGenerator,
7
7
  )
8
- from notionary.blocks.text_inline_formatter import TextInlineFormatter
8
+ from notionary.blocks import TextInlineFormatter
9
9
 
10
10
  from notionary.blocks import NotionBlockElement
11
11
  from notionary.telemetry import (
@@ -25,7 +25,6 @@ class BlockRegistry:
25
25
 
26
26
  Args:
27
27
  elements: Initial elements to register
28
- builder: The builder that created this registry (optional)
29
28
  """
30
29
  self._elements: List[NotionBlockElement] = []
31
30
  self._element_types: Set[Type[NotionBlockElement]] = set()
@@ -36,6 +35,21 @@ class BlockRegistry:
36
35
 
37
36
  self.telemetry = ProductTelemetry()
38
37
 
38
+ @classmethod
39
+ def create_registry(cls) -> BlockRegistry:
40
+ """
41
+ Create a registry with all standard elements in recommended order.
42
+
43
+ This uses the BlockRegistryBuilder internally to construct a complete
44
+ registry with all available block types.
45
+
46
+ Returns:
47
+ BlockRegistry: A fully configured registry with all standard elements
48
+ """
49
+ from notionary.blocks import BlockRegistryBuilder
50
+
51
+ return BlockRegistryBuilder.create_registry()
52
+
39
53
  def register(self, element_class: Type[NotionBlockElement]) -> bool:
40
54
  """
41
55
  Register an element class.
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import List, Type, TYPE_CHECKING
2
+ from typing import Type, TYPE_CHECKING, Self
3
3
  from collections import OrderedDict
4
4
 
5
5
  from notionary.blocks import (
@@ -7,7 +7,7 @@ from notionary.blocks import (
7
7
  AudioElement,
8
8
  BulletedListElement,
9
9
  CalloutElement,
10
- CodeBlockElement,
10
+ CodeElement,
11
11
  ColumnElement,
12
12
  DividerElement,
13
13
  EmbedElement,
@@ -42,7 +42,7 @@ class BlockRegistryBuilder:
42
42
  self._elements = OrderedDict()
43
43
 
44
44
  @classmethod
45
- def create_full_registry(cls) -> BlockRegistry:
45
+ def create_registry(cls) -> BlockRegistry:
46
46
  """
47
47
  Start with all standard elements in recommended order.
48
48
  """
@@ -69,57 +69,7 @@ class BlockRegistryBuilder:
69
69
  .with_toggleable_heading_element()
70
70
  ).build()
71
71
 
72
- @classmethod
73
- def create_minimal_registry(cls) -> BlockRegistry:
74
- """
75
- Create a minimal registry with just essential text elements.
76
- Suitable for basic note-taking.
77
- """
78
- builder = cls()
79
- return (
80
- builder.with_paragraphs()
81
- .with_headings()
82
- .with_bulleted_list()
83
- .with_numbered_list()
84
- ).build()
85
-
86
- def add_element(
87
- self, element_class: Type[NotionBlockElement]
88
- ) -> BlockRegistryBuilder:
89
- """
90
- Add an element class to the registry configuration.
91
- If the element already exists, it's moved to the end.
92
-
93
- Args:
94
- element_class: The element class to add
95
-
96
- Returns:
97
- Self for method chaining
98
- """
99
- self._elements.pop(element_class.__name__, None)
100
- self._elements[element_class.__name__] = element_class
101
-
102
- return self
103
-
104
- def add_elements(
105
- self, element_classes: List[Type[NotionBlockElement]]
106
- ) -> BlockRegistryBuilder:
107
- """
108
- Add multiple element classes to the registry configuration.
109
-
110
- Args:
111
- element_classes: List of element classes to add
112
-
113
- Returns:
114
- Self for method chaining
115
- """
116
- for element_class in element_classes:
117
- self.add_element(element_class)
118
- return self
119
-
120
- def remove_element(
121
- self, element_class: Type[NotionBlockElement]
122
- ) -> BlockRegistryBuilder:
72
+ def remove_element(self, element_class: Type[NotionBlockElement]) -> Self:
123
73
  """
124
74
  Remove an element class from the registry configuration.
125
75
 
@@ -132,160 +82,97 @@ class BlockRegistryBuilder:
132
82
  self._elements.pop(element_class.__name__, None)
133
83
  return self
134
84
 
135
- def move_element_to_end(
136
- self, element_class: Type[NotionBlockElement]
137
- ) -> BlockRegistryBuilder:
138
- """
139
- Move an existing element to the end of the registry.
140
- If the element doesn't exist, it will be added.
85
+ # WITH methods (existing)
86
+ def with_paragraphs(self) -> Self:
87
+ """Add support for paragraph elements."""
88
+ return self._add_element(ParagraphElement)
141
89
 
142
- Args:
143
- element_class: The element class to move
90
+ def with_headings(self) -> Self:
91
+ """Add support for heading elements."""
92
+ return self._add_element(HeadingElement)
144
93
 
145
- Returns:
146
- Self for method chaining
147
- """
148
- return self.add_element(element_class)
94
+ def with_callouts(self) -> Self:
95
+ """Add support for callout elements."""
96
+ return self._add_element(CalloutElement)
149
97
 
150
- def _ensure_paragraph_at_end(self) -> None:
151
- """
152
- Internal method to ensure ParagraphElement is the last element in the registry.
153
- """
154
- if ParagraphElement.__name__ in self._elements:
155
- paragraph_class = self._elements.pop(ParagraphElement.__name__)
156
- self._elements[ParagraphElement.__name__] = paragraph_class
98
+ def with_code(self) -> Self:
99
+ """Add support for code blocks."""
100
+ return self._add_element(CodeElement)
157
101
 
158
- def with_paragraphs(self) -> BlockRegistryBuilder:
159
- """
160
- Add support for paragraph elements.
161
- """
162
- return self.add_element(ParagraphElement)
102
+ def with_dividers(self) -> Self:
103
+ """Add support for divider elements."""
104
+ return self._add_element(DividerElement)
163
105
 
164
- def with_headings(self) -> BlockRegistryBuilder:
165
- """
166
- Add support for heading elements.
167
- """
168
- return self.add_element(HeadingElement)
106
+ def with_tables(self) -> Self:
107
+ """Add support for tables."""
108
+ return self._add_element(TableElement)
169
109
 
170
- def with_callouts(self) -> BlockRegistryBuilder:
171
- """
172
- Add support for callout elements.
173
- """
174
- return self.add_element(CalloutElement)
110
+ def with_bulleted_list(self) -> Self:
111
+ """Add support for bulleted list elements (unordered lists)."""
112
+ return self._add_element(BulletedListElement)
175
113
 
176
- def with_code(self) -> BlockRegistryBuilder:
177
- """
178
- Add support for code blocks.
179
- """
180
- return self.add_element(CodeBlockElement)
114
+ def with_numbered_list(self) -> Self:
115
+ """Add support for numbered list elements (ordered lists)."""
116
+ return self._add_element(NumberedListElement)
181
117
 
182
- def with_dividers(self) -> BlockRegistryBuilder:
183
- """
184
- Add support for divider elements.
185
- """
186
- return self.add_element(DividerElement)
118
+ def with_toggles(self) -> Self:
119
+ """Add support for toggle elements."""
120
+ return self._add_element(ToggleElement)
187
121
 
188
- def with_tables(self) -> BlockRegistryBuilder:
189
- """
190
- Add support for tables.
191
- """
192
- return self.add_element(TableElement)
122
+ def with_quotes(self) -> Self:
123
+ """Add support for quote elements."""
124
+ return self._add_element(QuoteElement)
193
125
 
194
- def with_bulleted_list(self) -> BlockRegistryBuilder:
195
- """
196
- Add support for bulleted list elements (unordered lists).
197
- """
198
- return self.add_element(BulletedListElement)
126
+ def with_todos(self) -> Self:
127
+ """Add support for todo elements."""
128
+ return self._add_element(TodoElement)
199
129
 
200
- def with_numbered_list(self) -> BlockRegistryBuilder:
201
- """
202
- Add support for numbered list elements (ordered lists).
203
- """
204
- return self.add_element(NumberedListElement)
130
+ def with_bookmarks(self) -> Self:
131
+ """Add support for bookmark elements."""
132
+ return self._add_element(BookmarkElement)
205
133
 
206
- def with_toggles(self) -> BlockRegistryBuilder:
207
- """
208
- Add support for toggle elements.
209
- """
210
- return self.add_element(ToggleElement)
211
-
212
- def with_quotes(self) -> BlockRegistryBuilder:
213
- """
214
- Add support for quote elements.
215
- """
216
- return self.add_element(QuoteElement)
217
-
218
- def with_todos(self) -> BlockRegistryBuilder:
219
- """
220
- Add support for todo elements.
221
- """
222
- return self.add_element(TodoElement)
134
+ def with_images(self) -> Self:
135
+ """Add support for image elements."""
136
+ return self._add_element(ImageElement)
223
137
 
224
- def with_bookmarks(self) -> BlockRegistryBuilder:
225
- """
226
- Add support for bookmark elements.
227
- """
228
- return self.add_element(BookmarkElement)
138
+ def with_videos(self) -> Self:
139
+ """Add support for video elements."""
140
+ return self._add_element(VideoElement)
229
141
 
230
- def with_images(self) -> BlockRegistryBuilder:
231
- """
232
- Add support for image elements.
233
- """
234
- return self.add_element(ImageElement)
142
+ def with_embeds(self) -> Self:
143
+ """Add support for embed elements."""
144
+ return self._add_element(EmbedElement)
235
145
 
236
- def with_videos(self) -> BlockRegistryBuilder:
237
- """
238
- Add support for video elements.
239
- """
240
- return self.add_element(VideoElement)
146
+ def with_audio(self) -> Self:
147
+ """Add support for audio elements."""
148
+ return self._add_element(AudioElement)
241
149
 
242
- def with_embeds(self) -> BlockRegistryBuilder:
243
- """
244
- Add support for embed elements.
245
- """
246
- return self.add_element(EmbedElement)
150
+ def with_mention(self) -> Self:
151
+ """Add support for mention elements."""
152
+ return self._add_element(MentionElement)
247
153
 
248
- def with_audio(self) -> BlockRegistryBuilder:
249
- """
250
- Add support for audio elements.
251
- """
252
- return self.add_element(AudioElement)
154
+ def with_toggleable_heading_element(self) -> Self:
155
+ """Add support for toggleable heading elements."""
156
+ return self._add_element(ToggleableHeadingElement)
253
157
 
254
- def with_media_support(self) -> BlockRegistryBuilder:
255
- """
256
- Add support for media elements (images, videos, audio).
257
- """
258
- return self.with_images().with_videos().with_audio()
259
-
260
- def with_mention(self) -> BlockRegistryBuilder:
261
- return self.add_element(MentionElement)
262
-
263
- def with_toggleable_heading_element(self) -> BlockRegistryBuilder:
264
- return self.add_element(ToggleableHeadingElement)
265
-
266
- def with_columns(self) -> BlockRegistryBuilder:
267
- """
268
- Add support for column elements.
269
- """
270
- return self.add_element(ColumnElement)
158
+ def with_columns(self) -> Self:
159
+ """Add support for column elements."""
160
+ return self._add_element(ColumnElement)
271
161
 
272
162
  def build(self) -> BlockRegistry:
273
163
  """
274
164
  Build and return the configured BlockRegistry instance.
275
165
 
276
166
  This automatically ensures that ParagraphElement is at the end
277
- of the registry (if present) as a fallback element, unless
278
- this behavior was explicitly disabled.
167
+ of the registry as a fallback element.
279
168
 
280
169
  Returns:
281
170
  A configured BlockRegistry instance
282
171
  """
283
172
  from notionary.blocks import BlockRegistry
284
173
 
285
- if ParagraphElement.__name__ not in self._elements:
286
- self.add_element(ParagraphElement)
287
- else:
288
- self._ensure_paragraph_at_end()
174
+ # Ensure ParagraphElement is always present and at the end
175
+ self._ensure_paragraph_at_end()
289
176
 
290
177
  registry = BlockRegistry()
291
178
 
@@ -294,3 +181,28 @@ class BlockRegistryBuilder:
294
181
  registry.register(element_class)
295
182
 
296
183
  return registry
184
+
185
+ def _ensure_paragraph_at_end(self) -> None:
186
+ """
187
+ Internal method to ensure ParagraphElement is the last element in the registry.
188
+ If ParagraphElement is not present, it will be added.
189
+ """
190
+ # Remove if present, then always add at the end
191
+ self._elements.pop(ParagraphElement.__name__, None)
192
+ self._elements[ParagraphElement.__name__] = ParagraphElement
193
+
194
+ def _add_element(self, element_class: Type[NotionBlockElement]) -> Self:
195
+ """
196
+ Add an element class to the registry configuration.
197
+ If the element already exists, it's moved to the end.
198
+
199
+ Args:
200
+ element_class: The element class to add
201
+
202
+ Returns:
203
+ Self for method chaining
204
+ """
205
+ self._elements.pop(element_class.__name__, None)
206
+ self._elements[element_class.__name__] = element_class
207
+
208
+ return self
File without changes