notionary 0.2.16__py3-none-any.whl → 0.2.18__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 (137) hide show
  1. notionary/__init__.py +10 -5
  2. notionary/base_notion_client.py +18 -7
  3. notionary/blocks/__init__.py +55 -24
  4. notionary/blocks/audio/__init__.py +7 -0
  5. notionary/blocks/audio/audio_element.py +152 -0
  6. notionary/blocks/audio/audio_markdown_node.py +29 -0
  7. notionary/blocks/audio/audio_models.py +59 -0
  8. notionary/blocks/bookmark/__init__.py +7 -0
  9. notionary/blocks/{bookmark_element.py → bookmark/bookmark_element.py} +20 -65
  10. notionary/blocks/bookmark/bookmark_markdown_node.py +43 -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 +710 -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/database/__init__.py +4 -0
  92. notionary/database/database.py +481 -0
  93. notionary/database/{filter_builder.py → database_filter_builder.py} +27 -29
  94. notionary/database/{notion_database_provider.py → database_provider.py} +4 -4
  95. notionary/database/notion_database.py +45 -18
  96. notionary/file_upload/__init__.py +7 -0
  97. notionary/file_upload/client.py +254 -0
  98. notionary/file_upload/models.py +60 -0
  99. notionary/file_upload/notion_file_upload.py +387 -0
  100. notionary/page/content/markdown_whitespace_processor.py +80 -0
  101. notionary/page/content/notion_text_length_utils.py +87 -0
  102. notionary/page/content/page_content_retriever.py +2 -2
  103. notionary/page/content/page_content_writer.py +97 -148
  104. notionary/page/formatting/line_processor.py +153 -0
  105. notionary/page/formatting/markdown_to_notion_converter.py +103 -424
  106. notionary/page/notion_page.py +13 -14
  107. notionary/page/notion_to_markdown_converter.py +9 -13
  108. notionary/telemetry/views.py +15 -6
  109. notionary/user/__init__.py +11 -0
  110. notionary/user/base_notion_user.py +52 -0
  111. notionary/user/client.py +129 -0
  112. notionary/user/models.py +83 -0
  113. notionary/user/notion_bot_user.py +227 -0
  114. notionary/user/notion_user.py +256 -0
  115. notionary/user/notion_user_manager.py +173 -0
  116. notionary/user/notion_user_provider.py +1 -0
  117. notionary/util/__init__.py +3 -5
  118. notionary/util/factory_decorator.py +0 -33
  119. notionary/util/factory_only.py +37 -0
  120. notionary/util/fuzzy.py +74 -0
  121. notionary/util/logging_mixin.py +12 -12
  122. notionary/workspace.py +38 -3
  123. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/METADATA +2 -1
  124. notionary-0.2.18.dist-info/RECORD +149 -0
  125. notionary/blocks/audio_element.py +0 -144
  126. notionary/blocks/callout_element.py +0 -122
  127. notionary/blocks/notion_block_client.py +0 -26
  128. notionary/blocks/qoute_element.py +0 -169
  129. notionary/page/content/notion_page_content_chunker.py +0 -84
  130. notionary/page/formatting/spacer_rules.py +0 -483
  131. notionary/util/fuzzy_matcher.py +0 -82
  132. notionary-0.2.16.dist-info/RECORD +0 -71
  133. /notionary/{elements/__init__.py → blocks/bookmark/bookmark_models.py} +0 -0
  134. /notionary/database/{database_exceptions.py → exceptions.py} +0 -0
  135. /notionary/util/{singleton_decorator.py → singleton.py} +0 -0
  136. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/LICENSE +0 -0
  137. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/WHEEL +0 -0
@@ -1,15 +1,19 @@
1
1
  import re
2
- from typing import Dict, Any, Optional
2
+ from typing import Any, Optional
3
3
  from notionary.blocks import NotionBlockElement
4
- from notionary.blocks import ElementPromptContent, ElementPromptBuilder
5
- from notionary.blocks.text_inline_formatter import TextInlineFormatter
4
+ from notionary.blocks import (
5
+ ElementPromptContent,
6
+ ElementPromptBuilder,
7
+ NotionBlockResult,
8
+ )
9
+ from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
6
10
 
7
11
 
8
12
  class NumberedListElement(NotionBlockElement):
9
13
  """Class for converting between Markdown numbered lists and Notion numbered list items."""
10
14
 
11
15
  @classmethod
12
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
16
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
13
17
  """Convert markdown numbered list item to Notion block."""
14
18
  pattern = re.compile(r"^\s*(\d+)\.\s+(.+)$")
15
19
  numbered_match = pattern.match(text)
@@ -27,7 +31,7 @@ class NumberedListElement(NotionBlockElement):
27
31
  }
28
32
 
29
33
  @classmethod
30
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
34
+ def notion_to_markdown(cls, block: dict[str, Any]) -> Optional[str]:
31
35
  """Convert Notion numbered list item block to markdown."""
32
36
  if block.get("type") != "numbered_list_item":
33
37
  return None
@@ -44,7 +48,7 @@ class NumberedListElement(NotionBlockElement):
44
48
  return bool(pattern.match(text))
45
49
 
46
50
  @classmethod
47
- def match_notion(cls, block: Dict[str, Any]) -> bool:
51
+ def match_notion(cls, block: dict[str, Any]) -> bool:
48
52
  """Check if this element can handle the given Notion block."""
49
53
  return block.get("type") == "numbered_list_item"
50
54
 
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+ from pydantic import BaseModel
3
+ from notionary.blocks.markdown_node import MarkdownNode
4
+
5
+
6
+ class NumberedListMarkdownBlockParams(BaseModel):
7
+ texts: list[str]
8
+
9
+
10
+ class NumberedListMarkdownNode(MarkdownNode):
11
+ """
12
+ Programmatic interface for creating Markdown numbered list items.
13
+ Example:
14
+ 1. First step
15
+ 2. Second step
16
+ 3. Third step
17
+ """
18
+
19
+ def __init__(self, texts: list[str]):
20
+ self.texts = texts
21
+
22
+ @classmethod
23
+ def from_params(
24
+ cls, params: NumberedListMarkdownBlockParams
25
+ ) -> NumberedListMarkdownNode:
26
+ return cls(texts=params.texts)
27
+
28
+ def to_markdown(self) -> str:
29
+ return "\n".join(f"{i + 1}. {text}" for i, text in enumerate(self.texts))
File without changes
@@ -0,0 +1,7 @@
1
+ from .paragraph_element import ParagraphElement
2
+ from .paragraph_markdown_node import ParagraphMarkdownNode
3
+
4
+ __all__ = [
5
+ "ParagraphElement",
6
+ "ParagraphMarkdownNode",
7
+ ]
@@ -1,8 +1,12 @@
1
1
  from typing import Dict, Any, Optional
2
2
 
3
3
  from notionary.blocks import NotionBlockElement
4
- from notionary.blocks import ElementPromptContent, ElementPromptBuilder
5
- from notionary.blocks.text_inline_formatter import TextInlineFormatter
4
+ from notionary.blocks import (
5
+ ElementPromptContent,
6
+ ElementPromptBuilder,
7
+ NotionBlockResult,
8
+ )
9
+ from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
6
10
 
7
11
 
8
12
  class ParagraphElement(NotionBlockElement):
@@ -23,7 +27,7 @@ class ParagraphElement(NotionBlockElement):
23
27
  return block.get("type") == "paragraph"
24
28
 
25
29
  @classmethod
26
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
30
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
27
31
  """Convert markdown paragraph to Notion paragraph block."""
28
32
  if not text.strip():
29
33
  return None
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+ from notionary.blocks.markdown_node import MarkdownNode
5
+
6
+
7
+ class ParagraphMarkdownBlockParams(BaseModel):
8
+ text: str
9
+
10
+
11
+ class ParagraphMarkdownNode(MarkdownNode):
12
+ """
13
+ Programmatic interface for creating Markdown paragraphs.
14
+ Paragraphs are standard text without special block formatting.
15
+ """
16
+
17
+ def __init__(self, text: str):
18
+ self.text = text
19
+
20
+ @classmethod
21
+ def from_params(cls, params: ParagraphMarkdownBlockParams) -> ParagraphMarkdownNode:
22
+ return cls(text=params.text)
23
+
24
+ def to_markdown(self) -> str:
25
+ return self.text
File without changes
@@ -0,0 +1,7 @@
1
+ from .quote_element import QuoteElement
2
+ from .quote_markdown_node import QuoteMarkdownNode
3
+
4
+ __all__ = [
5
+ "QuoteElement",
6
+ "QuoteMarkdownNode",
7
+ ]
@@ -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