notionary 0.2.19__py3-none-any.whl → 0.2.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. notionary/__init__.py +8 -4
  2. notionary/base_notion_client.py +3 -1
  3. notionary/blocks/__init__.py +2 -91
  4. notionary/blocks/_bootstrap.py +271 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +69 -106
  7. notionary/blocks/audio/audio_markdown_node.py +13 -5
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +42 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +49 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
  13. notionary/blocks/bookmark/bookmark_models.py +15 -0
  14. notionary/blocks/breadcrumbs/__init__.py +17 -0
  15. notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
  16. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
  17. notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
  18. notionary/blocks/bulleted_list/__init__.py +12 -2
  19. notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
  20. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
  21. notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
  22. notionary/blocks/callout/__init__.py +9 -2
  23. notionary/blocks/callout/callout_element.py +53 -86
  24. notionary/blocks/callout/callout_markdown_node.py +3 -1
  25. notionary/blocks/callout/callout_models.py +33 -0
  26. notionary/blocks/child_database/__init__.py +14 -0
  27. notionary/blocks/child_database/child_database_element.py +61 -0
  28. notionary/blocks/child_database/child_database_models.py +12 -0
  29. notionary/blocks/child_page/__init__.py +9 -0
  30. notionary/blocks/child_page/child_page_element.py +94 -0
  31. notionary/blocks/child_page/child_page_models.py +12 -0
  32. notionary/blocks/{shared/block_client.py → client.py} +54 -54
  33. notionary/blocks/code/__init__.py +6 -2
  34. notionary/blocks/code/code_element.py +96 -181
  35. notionary/blocks/code/code_markdown_node.py +64 -13
  36. notionary/blocks/code/code_models.py +94 -0
  37. notionary/blocks/column/__init__.py +25 -1
  38. notionary/blocks/column/column_element.py +44 -312
  39. notionary/blocks/column/column_list_element.py +52 -0
  40. notionary/blocks/column/column_list_markdown_node.py +50 -0
  41. notionary/blocks/column/column_markdown_node.py +59 -0
  42. notionary/blocks/column/column_models.py +26 -0
  43. notionary/blocks/divider/__init__.py +9 -2
  44. notionary/blocks/divider/divider_element.py +18 -49
  45. notionary/blocks/divider/divider_markdown_node.py +2 -1
  46. notionary/blocks/divider/divider_models.py +12 -0
  47. notionary/blocks/embed/__init__.py +9 -2
  48. notionary/blocks/embed/embed_element.py +65 -111
  49. notionary/blocks/embed/embed_markdown_node.py +3 -1
  50. notionary/blocks/embed/embed_models.py +14 -0
  51. notionary/blocks/equation/__init__.py +14 -0
  52. notionary/blocks/equation/equation_element.py +133 -0
  53. notionary/blocks/equation/equation_element_markdown_node.py +35 -0
  54. notionary/blocks/equation/equation_models.py +11 -0
  55. notionary/blocks/file/__init__.py +25 -0
  56. notionary/blocks/file/file_element.py +112 -0
  57. notionary/blocks/file/file_element_markdown_node.py +37 -0
  58. notionary/blocks/file/file_element_models.py +39 -0
  59. notionary/blocks/guards.py +22 -0
  60. notionary/blocks/heading/__init__.py +16 -2
  61. notionary/blocks/heading/heading_element.py +83 -69
  62. notionary/blocks/heading/heading_markdown_node.py +2 -1
  63. notionary/blocks/heading/heading_models.py +29 -0
  64. notionary/blocks/image_block/__init__.py +13 -0
  65. notionary/blocks/image_block/image_element.py +89 -0
  66. notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
  67. notionary/blocks/image_block/image_models.py +10 -0
  68. notionary/blocks/mixins/captions/__init__.py +4 -0
  69. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
  70. notionary/blocks/mixins/captions/caption_mixin.py +92 -0
  71. notionary/blocks/models.py +174 -0
  72. notionary/blocks/numbered_list/__init__.py +12 -2
  73. notionary/blocks/numbered_list/numbered_list_element.py +48 -56
  74. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  75. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  76. notionary/blocks/paragraph/__init__.py +12 -2
  77. notionary/blocks/paragraph/paragraph_element.py +40 -66
  78. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  79. notionary/blocks/paragraph/paragraph_models.py +16 -0
  80. notionary/blocks/pdf/__init__.py +13 -0
  81. notionary/blocks/pdf/pdf_element.py +97 -0
  82. notionary/blocks/pdf/pdf_markdown_node.py +37 -0
  83. notionary/blocks/pdf/pdf_models.py +11 -0
  84. notionary/blocks/quote/__init__.py +11 -2
  85. notionary/blocks/quote/quote_element.py +45 -62
  86. notionary/blocks/quote/quote_markdown_node.py +6 -3
  87. notionary/blocks/quote/quote_models.py +18 -0
  88. notionary/blocks/registry/__init__.py +4 -0
  89. notionary/blocks/registry/block_registry.py +60 -121
  90. notionary/blocks/registry/block_registry_builder.py +115 -59
  91. notionary/blocks/rich_text/__init__.py +33 -0
  92. notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
  93. notionary/blocks/rich_text/rich_text_models.py +221 -0
  94. notionary/blocks/rich_text/text_inline_formatter.py +456 -0
  95. notionary/blocks/syntax_prompt_builder.py +137 -0
  96. notionary/blocks/table/__init__.py +16 -2
  97. notionary/blocks/table/table_element.py +136 -228
  98. notionary/blocks/table/table_markdown_node.py +2 -1
  99. notionary/blocks/table/table_models.py +28 -0
  100. notionary/blocks/table_of_contents/__init__.py +19 -0
  101. notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
  102. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  103. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  104. notionary/blocks/todo/__init__.py +9 -2
  105. notionary/blocks/todo/todo_element.py +52 -92
  106. notionary/blocks/todo/todo_markdown_node.py +2 -1
  107. notionary/blocks/todo/todo_models.py +19 -0
  108. notionary/blocks/toggle/__init__.py +13 -3
  109. notionary/blocks/toggle/toggle_element.py +69 -260
  110. notionary/blocks/toggle/toggle_markdown_node.py +25 -15
  111. notionary/blocks/toggle/toggle_models.py +17 -0
  112. notionary/blocks/toggleable_heading/__init__.py +6 -2
  113. notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
  114. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  115. notionary/blocks/types.py +130 -0
  116. notionary/blocks/video/__init__.py +8 -2
  117. notionary/blocks/video/video_element.py +70 -141
  118. notionary/blocks/video/video_element_models.py +10 -0
  119. notionary/blocks/video/video_markdown_node.py +13 -6
  120. notionary/database/client.py +26 -8
  121. notionary/database/database.py +13 -14
  122. notionary/database/database_filter_builder.py +2 -2
  123. notionary/database/database_provider.py +5 -4
  124. notionary/database/models.py +337 -0
  125. notionary/database/notion_database.py +6 -7
  126. notionary/file_upload/client.py +5 -7
  127. notionary/file_upload/models.py +3 -2
  128. notionary/file_upload/notion_file_upload.py +2 -3
  129. notionary/markdown/markdown_builder.py +729 -0
  130. notionary/markdown/markdown_document_model.py +228 -0
  131. notionary/{blocks → markdown}/markdown_node.py +1 -0
  132. notionary/models/notion_database_response.py +0 -338
  133. notionary/page/client.py +34 -15
  134. notionary/page/models.py +327 -0
  135. notionary/page/notion_page.py +136 -58
  136. notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
  137. notionary/page/page_content_writer.py +177 -0
  138. notionary/page/page_context.py +65 -0
  139. notionary/page/reader/handler/__init__.py +19 -0
  140. notionary/page/reader/handler/base_block_renderer.py +44 -0
  141. notionary/page/reader/handler/block_processing_context.py +35 -0
  142. notionary/page/reader/handler/block_rendering_context.py +48 -0
  143. notionary/page/reader/handler/column_list_renderer.py +51 -0
  144. notionary/page/reader/handler/column_renderer.py +60 -0
  145. notionary/page/reader/handler/line_renderer.py +73 -0
  146. notionary/page/reader/handler/numbered_list_renderer.py +85 -0
  147. notionary/page/reader/handler/toggle_renderer.py +69 -0
  148. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  149. notionary/page/reader/page_content_retriever.py +81 -0
  150. notionary/page/search_filter_builder.py +2 -1
  151. notionary/page/writer/handler/__init__.py +24 -0
  152. notionary/page/writer/handler/code_handler.py +72 -0
  153. notionary/page/writer/handler/column_handler.py +141 -0
  154. notionary/page/writer/handler/column_list_handler.py +139 -0
  155. notionary/page/writer/handler/equation_handler.py +74 -0
  156. notionary/page/writer/handler/line_handler.py +35 -0
  157. notionary/page/writer/handler/line_processing_context.py +54 -0
  158. notionary/page/writer/handler/regular_line_handler.py +86 -0
  159. notionary/page/writer/handler/table_handler.py +66 -0
  160. notionary/page/writer/handler/toggle_handler.py +155 -0
  161. notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
  162. notionary/page/writer/markdown_to_notion_converter.py +95 -0
  163. notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
  164. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
  165. notionary/page/writer/notion_text_length_processor.py +150 -0
  166. notionary/telemetry/__init__.py +2 -2
  167. notionary/telemetry/service.py +3 -3
  168. notionary/user/__init__.py +2 -2
  169. notionary/user/base_notion_user.py +2 -1
  170. notionary/user/client.py +2 -3
  171. notionary/user/models.py +1 -0
  172. notionary/user/notion_bot_user.py +4 -5
  173. notionary/user/notion_user.py +3 -4
  174. notionary/user/notion_user_manager.py +23 -95
  175. notionary/util/__init__.py +3 -2
  176. notionary/util/fuzzy.py +2 -1
  177. notionary/util/logging_mixin.py +2 -2
  178. notionary/util/singleton_metaclass.py +1 -1
  179. notionary/workspace.py +6 -5
  180. notionary-0.2.22.dist-info/METADATA +237 -0
  181. notionary-0.2.22.dist-info/RECORD +200 -0
  182. notionary/blocks/document/__init__.py +0 -7
  183. notionary/blocks/document/document_element.py +0 -102
  184. notionary/blocks/document/document_markdown_node.py +0 -31
  185. notionary/blocks/image/__init__.py +0 -7
  186. notionary/blocks/image/image_element.py +0 -151
  187. notionary/blocks/markdown_builder.py +0 -356
  188. notionary/blocks/mention/__init__.py +0 -7
  189. notionary/blocks/mention/mention_element.py +0 -229
  190. notionary/blocks/mention/mention_markdown_node.py +0 -38
  191. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  192. notionary/blocks/prompts/element_prompt_content.py +0 -41
  193. notionary/blocks/shared/models.py +0 -713
  194. notionary/blocks/shared/notion_block_element.py +0 -37
  195. notionary/blocks/shared/text_inline_formatter.py +0 -262
  196. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  197. notionary/database/models/page_result.py +0 -10
  198. notionary/models/notion_block_response.py +0 -264
  199. notionary/models/notion_page_response.py +0 -78
  200. notionary/models/search_response.py +0 -0
  201. notionary/page/__init__.py +0 -0
  202. notionary/page/content/markdown_whitespace_processor.py +0 -80
  203. notionary/page/content/notion_text_length_utils.py +0 -87
  204. notionary/page/content/page_content_retriever.py +0 -60
  205. notionary/page/formatting/line_processor.py +0 -153
  206. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  207. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  208. notionary/page/notion_to_markdown_converter.py +0 -179
  209. notionary/page/properites/property_value_extractor.py +0 -0
  210. notionary/user/notion_user_provider.py +0 -1
  211. notionary-0.2.19.dist-info/METADATA +0 -225
  212. notionary-0.2.19.dist-info/RECORD +0 -150
  213. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  214. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  215. /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
  216. /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
  217. /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
  218. /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
  219. {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
  220. {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -1,32 +1,34 @@
1
1
  from __future__ import annotations
2
- from typing import Type, TYPE_CHECKING, Self
3
- from collections import OrderedDict
4
2
 
5
- from notionary.blocks import (
6
- ParagraphElement,
7
- AudioElement,
8
- BulletedListElement,
9
- CalloutElement,
10
- CodeElement,
11
- ColumnElement,
12
- DividerElement,
13
- EmbedElement,
14
- HeadingElement,
15
- ImageElement,
16
- MentionElement,
17
- NumberedListElement,
18
- TableElement,
19
- TodoElement,
20
- ToggleElement,
21
- ToggleableHeadingElement,
22
- VideoElement,
23
- BookmarkElement,
24
- QuoteElement,
25
- NotionBlockElement,
26
- )
3
+ from collections import OrderedDict
4
+ from typing import TYPE_CHECKING, Self, Type
5
+
6
+ from notionary.blocks.audio import AudioElement
7
+ from notionary.blocks.base_block_element import BaseBlockElement
8
+ from notionary.blocks.bookmark import BookmarkElement
9
+ from notionary.blocks.breadcrumbs import BreadcrumbElement
10
+ from notionary.blocks.bulleted_list import BulletedListElement
11
+ from notionary.blocks.callout import CalloutElement
12
+ from notionary.blocks.child_database import ChildDatabaseElement
13
+ from notionary.blocks.code import CodeElement
14
+ from notionary.blocks.column import ColumnElement, ColumnListElement
15
+ from notionary.blocks.divider import DividerElement
16
+ from notionary.blocks.embed import EmbedElement
17
+ from notionary.blocks.equation import EquationElement
18
+ from notionary.blocks.heading import HeadingElement
19
+ from notionary.blocks.image_block import ImageElement
20
+ from notionary.blocks.numbered_list import NumberedListElement
21
+ from notionary.blocks.paragraph import ParagraphElement
22
+ from notionary.blocks.quote import QuoteElement
23
+ from notionary.blocks.table import TableElement
24
+ from notionary.blocks.table_of_contents import TableOfContentsElement
25
+ from notionary.blocks.todo import TodoElement
26
+ from notionary.blocks.toggle import ToggleElement
27
+ from notionary.blocks.toggleable_heading import ToggleableHeadingElement
28
+ from notionary.blocks.video import VideoElement
27
29
 
28
30
  if TYPE_CHECKING:
29
- from notionary.blocks import BlockRegistry
31
+ from notionary.blocks.registry.block_registry import BlockRegistry
30
32
 
31
33
 
32
34
  class BlockRegistryBuilder:
@@ -63,13 +65,16 @@ class BlockRegistryBuilder:
63
65
  .with_videos()
64
66
  .with_embeds()
65
67
  .with_audio()
66
- .with_columns()
67
- .with_mention()
68
68
  .with_paragraphs()
69
69
  .with_toggleable_heading_element()
70
+ .with_columns()
71
+ .with_equation()
72
+ .with_table_of_contents()
73
+ .with_breadcrumbs()
74
+ .with_child_database()
70
75
  ).build()
71
76
 
72
- def remove_element(self, element_class: Type[NotionBlockElement]) -> Self:
77
+ def remove_element(self, element_class: Type[BaseBlockElement]) -> Self:
73
78
  """
74
79
  Remove an element class from the registry configuration.
75
80
 
@@ -84,92 +89,143 @@ class BlockRegistryBuilder:
84
89
 
85
90
  # WITH methods (existing)
86
91
  def with_paragraphs(self) -> Self:
87
- """Add support for paragraph elements."""
88
92
  return self._add_element(ParagraphElement)
89
93
 
90
94
  def with_headings(self) -> Self:
91
- """Add support for heading elements."""
92
95
  return self._add_element(HeadingElement)
93
96
 
94
97
  def with_callouts(self) -> Self:
95
- """Add support for callout elements."""
96
98
  return self._add_element(CalloutElement)
97
99
 
98
100
  def with_code(self) -> Self:
99
- """Add support for code blocks."""
100
101
  return self._add_element(CodeElement)
101
102
 
102
103
  def with_dividers(self) -> Self:
103
- """Add support for divider elements."""
104
104
  return self._add_element(DividerElement)
105
105
 
106
106
  def with_tables(self) -> Self:
107
- """Add support for tables."""
108
107
  return self._add_element(TableElement)
109
108
 
110
109
  def with_bulleted_list(self) -> Self:
111
- """Add support for bulleted list elements (unordered lists)."""
112
110
  return self._add_element(BulletedListElement)
113
111
 
114
112
  def with_numbered_list(self) -> Self:
115
- """Add support for numbered list elements (ordered lists)."""
116
113
  return self._add_element(NumberedListElement)
117
114
 
118
115
  def with_toggles(self) -> Self:
119
- """Add support for toggle elements."""
120
116
  return self._add_element(ToggleElement)
121
117
 
122
118
  def with_quotes(self) -> Self:
123
- """Add support for quote elements."""
124
119
  return self._add_element(QuoteElement)
125
120
 
126
121
  def with_todos(self) -> Self:
127
- """Add support for todo elements."""
128
122
  return self._add_element(TodoElement)
129
123
 
130
124
  def with_bookmarks(self) -> Self:
131
- """Add support for bookmark elements."""
132
125
  return self._add_element(BookmarkElement)
133
126
 
134
127
  def with_images(self) -> Self:
135
- """Add support for image elements."""
136
128
  return self._add_element(ImageElement)
137
129
 
138
130
  def with_videos(self) -> Self:
139
- """Add support for video elements."""
140
131
  return self._add_element(VideoElement)
141
132
 
142
133
  def with_embeds(self) -> Self:
143
- """Add support for embed elements."""
144
134
  return self._add_element(EmbedElement)
145
135
 
146
136
  def with_audio(self) -> Self:
147
- """Add support for audio elements."""
148
137
  return self._add_element(AudioElement)
149
138
 
150
- def with_mention(self) -> Self:
151
- """Add support for mention elements."""
152
- return self._add_element(MentionElement)
153
-
154
139
  def with_toggleable_heading_element(self) -> Self:
155
- """Add support for toggleable heading elements."""
156
140
  return self._add_element(ToggleableHeadingElement)
157
141
 
158
142
  def with_columns(self) -> Self:
159
- """Add support for column elements."""
160
- return self._add_element(ColumnElement)
143
+ self._add_element(ColumnListElement)
144
+ self._add_element(ColumnElement)
145
+ return self
146
+
147
+ def with_equation(self) -> Self:
148
+ return self._add_element(EquationElement)
149
+
150
+ def with_table_of_contents(self) -> Self:
151
+ return self._add_element(TableOfContentsElement)
152
+
153
+ def with_breadcrumbs(self) -> Self:
154
+ return self._add_element(BreadcrumbElement)
155
+
156
+ def with_child_database(self) -> Self:
157
+ return self._add_element(ChildDatabaseElement)
158
+
159
+ def without_headings(self) -> Self:
160
+ return self.remove_element(HeadingElement)
161
+
162
+ def without_callouts(self) -> Self:
163
+ return self.remove_element(CalloutElement)
164
+
165
+ def without_code(self) -> Self:
166
+ return self.remove_element(CodeElement)
167
+
168
+ def without_dividers(self) -> Self:
169
+ return self.remove_element(DividerElement)
170
+
171
+ def without_tables(self) -> Self:
172
+ return self.remove_element(TableElement)
173
+
174
+ def without_bulleted_list(self) -> Self:
175
+ return self.remove_element(BulletedListElement)
176
+
177
+ def without_numbered_list(self) -> Self:
178
+ return self.remove_element(NumberedListElement)
179
+
180
+ def without_toggles(self) -> Self:
181
+ return self.remove_element(ToggleElement)
182
+
183
+ def without_quotes(self) -> Self:
184
+ return self.remove_element(QuoteElement)
185
+
186
+ def without_todos(self) -> Self:
187
+ return self.remove_element(TodoElement)
188
+
189
+ def without_bookmarks(self) -> Self:
190
+ return self.remove_element(BookmarkElement)
191
+
192
+ def without_images(self) -> Self:
193
+ return self.remove_element(ImageElement)
194
+
195
+ def without_videos(self) -> Self:
196
+ return self.remove_element(VideoElement)
197
+
198
+ def without_embeds(self) -> Self:
199
+ return self.remove_element(EmbedElement)
200
+
201
+ def without_audio(self) -> Self:
202
+ return self.remove_element(AudioElement)
203
+
204
+ def without_toggleable_heading_element(self) -> Self:
205
+ return self.remove_element(ToggleableHeadingElement)
206
+
207
+ def without_columns(self) -> Self:
208
+ self.remove_element(ColumnListElement)
209
+ self.remove_element(ColumnElement)
210
+ return self
211
+
212
+ def without_equation(self) -> Self:
213
+ return self.remove_element(EquationElement)
214
+
215
+ def without_table_of_contents(self) -> Self:
216
+ return self.remove_element(TableOfContentsElement)
217
+
218
+ def without_breadcrumbs(self) -> Self:
219
+ return self.remove_element(BreadcrumbElement)
220
+
221
+ def without_child_database(self) -> Self:
222
+ return self.remove_element(ChildDatabaseElement)
161
223
 
162
224
  def build(self) -> BlockRegistry:
163
225
  """
164
226
  Build and return the configured BlockRegistry instance.
165
-
166
- This automatically ensures that ParagraphElement is at the end
167
- of the registry as a fallback element.
168
-
169
- Returns:
170
- A configured BlockRegistry instance
171
227
  """
172
- from notionary.blocks import BlockRegistry
228
+ from notionary.blocks.registry.block_registry import BlockRegistry
173
229
 
174
230
  # Ensure ParagraphElement is always present and at the end
175
231
  self._ensure_paragraph_at_end()
@@ -191,7 +247,7 @@ class BlockRegistryBuilder:
191
247
  self._elements.pop(ParagraphElement.__name__, None)
192
248
  self._elements[ParagraphElement.__name__] = ParagraphElement
193
249
 
194
- def _add_element(self, element_class: Type[NotionBlockElement]) -> Self:
250
+ def _add_element(self, element_class: Type[BaseBlockElement]) -> Self:
195
251
  """
196
252
  Add an element class to the registry configuration.
197
253
  If the element already exists, it's moved to the end.
@@ -0,0 +1,33 @@
1
+ """Rich text handling for Notionary."""
2
+
3
+ from notionary.blocks.rich_text.rich_text_models import (
4
+ EquationObject,
5
+ LinkObject,
6
+ MentionDatabaseRef,
7
+ MentionDate,
8
+ MentionLinkPreview,
9
+ MentionObject,
10
+ MentionPageRef,
11
+ MentionTemplateMention,
12
+ MentionUserRef,
13
+ RichTextObject,
14
+ TextAnnotations,
15
+ TextContent,
16
+ )
17
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
18
+
19
+ __all__ = [
20
+ "RichTextObject",
21
+ "TextAnnotations",
22
+ "LinkObject",
23
+ "TextContent",
24
+ "EquationObject",
25
+ "MentionUserRef",
26
+ "MentionPageRef",
27
+ "MentionDatabaseRef",
28
+ "MentionLinkPreview",
29
+ "MentionDate",
30
+ "MentionTemplateMention",
31
+ "MentionObject",
32
+ "TextInlineFormatter",
33
+ ]
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from contextlib import contextmanager
5
+ from typing import Optional
6
+
7
+ from notionary.user.notion_user_manager import NotionUserManager
8
+ from notionary.util import format_uuid
9
+ from notionary.util.fuzzy import find_best_match
10
+
11
+
12
+ class NameIdResolver:
13
+ """
14
+ Bidirectional resolver for Notion page and database names and IDs.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ token: Optional[str] = None,
21
+ search_limit: int = 10,
22
+ ):
23
+ """
24
+ Initialize the resolver with a Notion workspace.
25
+ """
26
+ from notionary import NotionWorkspace
27
+
28
+ self.workspace = NotionWorkspace(token=token)
29
+ self.notion_user_manager = NotionUserManager(token=token)
30
+ self.search_limit = search_limit
31
+
32
+ async def resolve_page_id(self, name: str) -> Optional[str]:
33
+ """
34
+ Convert a page name to its Notion page ID.
35
+ Specifically searches only pages, not databases.
36
+ """
37
+ if not name:
38
+ return None
39
+
40
+ cleaned_name = name.strip()
41
+
42
+ # Return if already a valid Notion ID
43
+ formatted_uuid = format_uuid(cleaned_name)
44
+ if formatted_uuid:
45
+ return formatted_uuid
46
+
47
+ # Search for page by name
48
+ return await self._resolve_page_id(cleaned_name)
49
+
50
+ async def resolve_database_id(self, name: str) -> Optional[str]:
51
+ """
52
+ Convert a database name to its Notion database ID.
53
+ Specifically searches only databases, not pages.
54
+ """
55
+ if not name:
56
+ return None
57
+
58
+ cleaned_name = name.strip()
59
+
60
+ formatted_uuid = format_uuid(cleaned_name)
61
+ if formatted_uuid:
62
+ return formatted_uuid
63
+
64
+ return await self._resolve_database_id(cleaned_name)
65
+
66
+ async def resolve_page_name(self, page_id: str) -> Optional[str]:
67
+ """
68
+ Convert a Notion page ID to its human-readable title.
69
+ """
70
+ if not page_id:
71
+ return None
72
+
73
+ formatted_id = format_uuid(page_id)
74
+ if not formatted_id:
75
+ return None
76
+
77
+ try:
78
+ from notionary import NotionPage
79
+
80
+ page = await NotionPage.from_page_id(formatted_id)
81
+ return page.title if page else None
82
+ except Exception:
83
+ return None
84
+
85
+ async def resolve_database_name(self, database_id: str) -> Optional[str]:
86
+ """
87
+ Convert a Notion database ID to its human-readable title.
88
+ """
89
+ if not database_id:
90
+ return None
91
+
92
+ # Validate and format UUID
93
+ formatted_id = format_uuid(database_id)
94
+ if not formatted_id:
95
+ return None
96
+
97
+ try:
98
+ from notionary.database import NotionDatabase
99
+
100
+ database = await NotionDatabase.from_database_id(formatted_id)
101
+ return database.title if database else None
102
+ except Exception:
103
+ return None
104
+
105
+ async def resolve_user_id(self, name: str) -> Optional[str]:
106
+ """
107
+ Convert a user name to its Notion user ID.
108
+ Specifically searches only users.
109
+ """
110
+ if not name:
111
+ return None
112
+
113
+ cleaned_name = name.strip()
114
+
115
+ # Return if already a valid Notion ID
116
+ formatted_uuid = format_uuid(cleaned_name)
117
+ if formatted_uuid:
118
+ return formatted_uuid
119
+
120
+ # Search for user by name
121
+ return await self._resolve_user_id(cleaned_name)
122
+
123
+ async def resolve_user_name(self, user_id: str) -> Optional[str]:
124
+ """
125
+ Convert a Notion user ID to its human-readable name.
126
+
127
+ Args:
128
+ user_id: Notion user ID to resolve
129
+
130
+ Returns:
131
+ User name if found, None if not found or inaccessible
132
+ """
133
+ if not user_id:
134
+ return None
135
+
136
+ # Validate and format UUID
137
+ formatted_id = format_uuid(user_id)
138
+ if not formatted_id:
139
+ return None
140
+
141
+ try:
142
+ user = await self.notion_user_manager.get_user_by_id(formatted_id)
143
+ return user.name if user else None
144
+ except Exception:
145
+ return None
146
+
147
+ async def _resolve_user_id(self, name: str) -> Optional[str]:
148
+ """Search for users matching the name."""
149
+ try:
150
+ users = await self.notion_user_manager.find_users_by_name(name)
151
+
152
+ if not users:
153
+ return None
154
+
155
+ # Use fuzzy matching to find best match
156
+ best_match = find_best_match(
157
+ query=name,
158
+ items=users,
159
+ text_extractor=lambda user: user.name or "",
160
+ )
161
+
162
+ return best_match.item.id if best_match else None
163
+ except Exception:
164
+ return None
165
+
166
+ async def _resolve_page_id(self, name: str) -> Optional[str]:
167
+ """Search for pages matching the name."""
168
+ search_results = await self.workspace.search_pages(
169
+ query=name, limit=self.search_limit
170
+ )
171
+
172
+ return self._find_best_fuzzy_match(query=name, candidate_objects=search_results)
173
+
174
+ async def _resolve_database_id(self, name: str) -> Optional[str]:
175
+ """Search for databases matching the name."""
176
+ search_results = await self.workspace.search_databases(
177
+ query=name, limit=self.search_limit
178
+ )
179
+
180
+ return self._find_best_fuzzy_match(query=name, candidate_objects=search_results)
181
+
182
+ def _find_best_fuzzy_match(
183
+ self, query: str, candidate_objects: list
184
+ ) -> Optional[str]:
185
+ """
186
+ Find the best fuzzy match among candidate objects using existing fuzzy matching logic.
187
+
188
+ Args:
189
+ query: The search query to match against
190
+ candidate_objects: Objects (pages or databases) with .id and .title attributes
191
+
192
+ Returns:
193
+ ID of best match, or None if no match meets threshold
194
+ """
195
+ if not candidate_objects:
196
+ return None
197
+
198
+ # Use existing fuzzy matching logic
199
+ best_match = find_best_match(
200
+ query=query,
201
+ items=candidate_objects,
202
+ text_extractor=lambda obj: obj.title,
203
+ )
204
+
205
+ return best_match.item.id if best_match else None