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
@@ -0,0 +1,327 @@
1
+ from typing import Any, Literal, Optional, Union
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class TextContent(BaseModel):
7
+ content: str
8
+ link: Optional[str] = None
9
+
10
+
11
+ class RichText(BaseModel):
12
+ type: str
13
+ text: TextContent
14
+ plain_text: str
15
+ href: Optional[str] = None
16
+
17
+
18
+ class User(BaseModel):
19
+ object: str
20
+ id: str
21
+
22
+
23
+ class Parent(BaseModel):
24
+ type: Literal["page_id", "workspace", "block_id", "database_id"]
25
+ page_id: Optional[str] = None
26
+ block_id: Optional[str] = None
27
+ database_id: Optional[str] = None
28
+
29
+
30
+ # Rich text types for Pydantic models
31
+ class TextContentPydantic(BaseModel):
32
+ content: str
33
+ link: Optional[dict[str, str]] = None
34
+
35
+
36
+ class Annotations(BaseModel):
37
+ bold: bool
38
+ italic: bool
39
+ strikethrough: bool
40
+ underline: bool
41
+ code: bool
42
+ color: str
43
+
44
+
45
+ class RichTextItemPydantic(BaseModel):
46
+ type: str # 'text', 'mention', 'equation'
47
+ text: Optional[TextContentPydantic] = None
48
+ annotations: Annotations
49
+ plain_text: str
50
+ href: Optional[str] = None
51
+
52
+
53
+ # Cover and Icon types
54
+ class ExternalFile(BaseModel):
55
+ """Represents an external file, e.g., for cover images."""
56
+
57
+ url: str
58
+
59
+
60
+ class Cover(BaseModel):
61
+ """Cover image for a Notion page."""
62
+
63
+ type: str
64
+ external: ExternalFile
65
+
66
+
67
+ class EmojiIcon(BaseModel):
68
+ type: Literal["emoji"]
69
+ emoji: str
70
+
71
+
72
+ class ExternalIcon(BaseModel):
73
+ type: Literal["external"]
74
+ external: ExternalFile
75
+
76
+
77
+ Icon = Union[EmojiIcon, ExternalIcon]
78
+
79
+
80
+ # Database property schema types (these are schema definitions, not values)
81
+ class StatusOption(BaseModel):
82
+ id: str
83
+ name: str
84
+ color: str
85
+ description: Optional[str] = None
86
+
87
+
88
+ class StatusGroup(BaseModel):
89
+ id: str
90
+ name: str
91
+ color: str
92
+ option_ids: list[str]
93
+
94
+
95
+ class StatusPropertySchema(BaseModel):
96
+ options: list[StatusOption]
97
+ groups: list[StatusGroup]
98
+
99
+
100
+ class DatabaseStatusProperty(BaseModel):
101
+ id: str
102
+ name: str
103
+ type: Literal["status"]
104
+ status: StatusPropertySchema
105
+
106
+
107
+ class RelationPropertySchema(BaseModel):
108
+ database_id: str
109
+ type: str # "single_property"
110
+ single_property: dict[str, Any]
111
+
112
+
113
+ class DatabaseRelationProperty(BaseModel):
114
+ id: str
115
+ name: str
116
+ type: Literal["relation"]
117
+ relation: RelationPropertySchema
118
+
119
+
120
+ class DatabaseUrlProperty(BaseModel):
121
+ id: str
122
+ name: str
123
+ type: Literal["url"]
124
+ url: dict[str, Any] # Usually empty dict
125
+
126
+
127
+ class DatabaseRichTextProperty(BaseModel):
128
+ id: str
129
+ name: str
130
+ type: Literal["rich_text"]
131
+ rich_text: dict[str, Any] # Usually empty dict
132
+
133
+
134
+ class MultiSelectOption(BaseModel):
135
+ id: str
136
+ name: str
137
+ color: str
138
+ description: Optional[str] = None
139
+
140
+
141
+ class MultiSelectPropertySchema(BaseModel):
142
+ options: list[MultiSelectOption]
143
+
144
+
145
+ class DatabaseMultiSelectProperty(BaseModel):
146
+ id: str
147
+ name: str
148
+ type: Literal["multi_select"]
149
+ multi_select: MultiSelectPropertySchema
150
+
151
+
152
+ class DatabaseTitleProperty(BaseModel):
153
+ id: str
154
+ name: str
155
+ type: Literal["title"]
156
+ title: dict[str, Any] # Usually empty dict
157
+
158
+
159
+ # Generic database property for unknown types
160
+ class GenericDatabaseProperty(BaseModel):
161
+ id: str
162
+ name: str
163
+ type: str
164
+
165
+ model_config = ConfigDict(extra="allow")
166
+
167
+
168
+ # Union of all database property types
169
+ DatabaseProperty = Union[
170
+ DatabaseStatusProperty,
171
+ DatabaseRelationProperty,
172
+ DatabaseUrlProperty,
173
+ DatabaseRichTextProperty,
174
+ DatabaseMultiSelectProperty,
175
+ DatabaseTitleProperty,
176
+ GenericDatabaseProperty,
177
+ ]
178
+
179
+
180
+ # Page property value types (these are actual values, not schemas)
181
+ class StatusValue(BaseModel):
182
+ id: str
183
+ name: str
184
+ color: str
185
+
186
+
187
+ class StatusProperty(BaseModel):
188
+ id: str
189
+ type: str # 'status'
190
+ status: Optional[StatusValue] = None
191
+
192
+
193
+ class RelationItem(BaseModel):
194
+ id: str
195
+
196
+
197
+ class RelationProperty(BaseModel):
198
+ id: str
199
+ type: str # 'relation'
200
+ relation: list[RelationItem]
201
+ has_more: bool
202
+
203
+
204
+ class UrlProperty(BaseModel):
205
+ id: str
206
+ type: str # 'url'
207
+ url: Optional[str] = None
208
+
209
+
210
+ class RichTextProperty(BaseModel):
211
+ id: str
212
+ type: str # 'rich_text'
213
+ rich_text: list[RichTextItemPydantic]
214
+
215
+
216
+ class MultiSelectItem(BaseModel):
217
+ id: str
218
+ name: str
219
+ color: str
220
+
221
+
222
+ class MultiSelectProperty(BaseModel):
223
+ id: str
224
+ type: str # 'multi_select'
225
+ multi_select: list[MultiSelectItem]
226
+
227
+
228
+ class TitleProperty(BaseModel):
229
+ id: str
230
+ type: str # 'title'
231
+ title: list[RichTextItemPydantic]
232
+
233
+
234
+ # Cover types
235
+ class ExternalCover(BaseModel):
236
+ url: str
237
+
238
+
239
+ class NotionCover(BaseModel):
240
+ type: str # 'external', 'file'
241
+ external: Optional[ExternalCover] = None
242
+
243
+
244
+ # Parent types for Pydantic
245
+ class NotionParent(BaseModel):
246
+ type: str # 'database_id', 'page_id', 'workspace'
247
+ database_id: Optional[str] = None
248
+ page_id: Optional[str] = None
249
+
250
+
251
+ # User type for Pydantic
252
+ class NotionUser(BaseModel):
253
+ object: str # 'user'
254
+ id: str
255
+
256
+
257
+ # Database object
258
+ class NotionDatabaseResponse(BaseModel):
259
+ """
260
+ Represents the response from the Notion API when retrieving a database.
261
+ """
262
+
263
+ object: Literal["database"]
264
+ id: str
265
+ cover: Optional[Cover] = None
266
+ icon: Optional[Icon] = None
267
+ created_time: str
268
+ last_edited_time: str
269
+ created_by: NotionUser
270
+ last_edited_by: NotionUser
271
+ title: list[RichTextItemPydantic]
272
+ description: list[Any]
273
+ is_inline: bool
274
+ properties: dict[
275
+ str, Any
276
+ ] # Using Any for flexibility with different property schemas
277
+ parent: NotionParent
278
+ url: str
279
+ public_url: Optional[str] = None
280
+ archived: bool
281
+ in_trash: bool
282
+
283
+
284
+ class NotionPageResponse(BaseModel):
285
+ object: Literal["page"]
286
+ id: str
287
+ created_time: str
288
+ last_edited_time: str
289
+ created_by: NotionUser
290
+ last_edited_by: NotionUser
291
+ cover: Optional[NotionCover] = None
292
+ icon: Optional[Icon] = None
293
+ parent: NotionParent
294
+ archived: bool
295
+ in_trash: bool
296
+ properties: dict[str, Any]
297
+ url: str
298
+ public_url: Optional[str] = None
299
+
300
+
301
+ # Specific response type for database queries (pages only)
302
+ class NotionQueryDatabaseResponse(BaseModel):
303
+ """
304
+ Notion database query response model for querying pages within a database.
305
+ """
306
+
307
+ object: Literal["list"]
308
+ results: list[NotionPageResponse]
309
+ next_cursor: Optional[str] = None
310
+ has_more: bool
311
+ type: Literal["page_or_database"]
312
+ page_or_database: dict[str, Any]
313
+ request_id: str
314
+
315
+
316
+ class NotionDatabaseSearchResponse(BaseModel):
317
+ """
318
+ Notion search response model for database-only searches.
319
+ """
320
+
321
+ object: Literal["list"]
322
+ results: list[NotionDatabaseResponse]
323
+ next_cursor: Optional[str] = None
324
+ has_more: bool
325
+ type: Literal["page_or_database"]
326
+ page_or_database: dict[str, Any]
327
+ request_id: str
@@ -1,40 +1,44 @@
1
1
  from __future__ import annotations
2
+
2
3
  import asyncio
3
- from typing import Any, Dict, Optional, TYPE_CHECKING
4
4
  import random
5
-
6
- from notionary.blocks import BlockRegistry
7
- from notionary.models.notion_database_response import NotionPageResponse
8
- from notionary.models.notion_page_response import DatabaseParent
5
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union
6
+
7
+ from notionary.blocks.client import NotionBlockClient
8
+ from notionary.blocks.syntax_prompt_builder import SyntaxPromptBuilder
9
+ from notionary.blocks.models import DatabaseParent
10
+ from notionary.blocks.registry.block_registry import BlockRegistry
11
+ from notionary.blocks.registry.block_registry_builder import BlockRegistryBuilder
12
+ from notionary.database.client import NotionDatabaseClient
13
+ from notionary.markdown.markdown_builder import MarkdownBuilder
9
14
  from notionary.page.client import NotionPageClient
10
- from notionary.page.content.page_content_retriever import PageContentRetriever
11
-
12
-
13
- from notionary.page.content.page_content_writer import PageContentWriter
15
+ from notionary.page.models import NotionPageResponse
16
+ from notionary.page.page_content_deleting_service import PageContentDeletingService
17
+ from notionary.page.page_content_writer import PageContentWriter
14
18
  from notionary.page.property_formatter import NotionPropertyFormatter
19
+ from notionary.page.reader.page_content_retriever import PageContentRetriever
15
20
  from notionary.page.utils import extract_property_value
16
-
17
- from notionary.util import LoggingMixin, format_uuid, factory_only
21
+ from notionary.util import LoggingMixin, extract_uuid, factory_only, format_uuid
18
22
  from notionary.util.fuzzy import find_best_match
19
23
 
20
-
21
24
  if TYPE_CHECKING:
22
25
  from notionary import NotionDatabase
23
26
 
24
-
25
27
  class NotionPage(LoggingMixin):
26
28
  """
27
29
  Managing content and metadata of a Notion page.
28
30
  """
29
31
 
30
- @factory_only("from_page_id", "from_page_name")
32
+ @factory_only("from_page_id", "from_page_name", "from_url")
31
33
  def __init__(
32
34
  self,
33
35
  page_id: str,
34
36
  title: str,
35
37
  url: str,
38
+ archived: bool,
39
+ in_trash: bool,
36
40
  emoji_icon: Optional[str] = None,
37
- properties: Optional[Dict[str, Any]] = None,
41
+ properties: Optional[dict[str, Any]] = None,
38
42
  parent_database: Optional[NotionDatabase] = None,
39
43
  token: Optional[str] = None,
40
44
  ):
@@ -44,23 +48,30 @@ class NotionPage(LoggingMixin):
44
48
  self._page_id = page_id
45
49
  self._title = title
46
50
  self._url = url
51
+ self._is_archived = archived
52
+ self._is_in_trash = in_trash
47
53
  self._emoji_icon = emoji_icon
48
54
  self._properties = properties
49
55
  self._parent_database = parent_database
50
56
 
51
57
  self._client = NotionPageClient(token=token)
58
+ self._block_client = NotionBlockClient(token=token)
52
59
  self._page_data = None
53
-
54
- self._block_element_registry = BlockRegistry.create_registry()
60
+
61
+ self.block_element_registry = BlockRegistry.create_registry()
55
62
 
56
63
  self._page_content_writer = PageContentWriter(
57
64
  page_id=self._page_id,
58
- block_registry=self._block_element_registry,
65
+ block_registry=self.block_element_registry,
59
66
  )
60
67
 
61
- self._page_content_retriever = PageContentRetriever(
68
+ self._page_content_deleting_service = PageContentDeletingService(
62
69
  page_id=self._page_id,
63
- block_registry=self._block_element_registry,
70
+ block_registry=self.block_element_registry,
71
+ )
72
+
73
+ self._page_content_retriever = PageContentRetriever(
74
+ block_registry=self.block_element_registry,
64
75
  )
65
76
 
66
77
  @classmethod
@@ -69,10 +80,6 @@ class NotionPage(LoggingMixin):
69
80
  ) -> NotionPage:
70
81
  """
71
82
  Create a NotionPage from a page ID.
72
-
73
- Args:
74
- page_id: The ID of the Notion page
75
- token: Optional Notion API token (uses environment variable if not provided)
76
83
  """
77
84
  formatted_id = format_uuid(page_id) or page_id
78
85
 
@@ -131,6 +138,26 @@ class NotionPage(LoggingMixin):
131
138
  cls.logger.error("Error finding page by name: %s", str(e))
132
139
  raise
133
140
 
141
+ @classmethod
142
+ async def from_url(cls, url: str, token: Optional[str] = None) -> NotionPage:
143
+ """
144
+ Create a NotionPage from a Notion page URL.
145
+ """
146
+ try:
147
+ page_id = extract_uuid(url)
148
+ if not page_id:
149
+ raise ValueError(f"Could not extract page ID from URL: {url}")
150
+
151
+ formatted_id = format_uuid(page_id) or page_id
152
+
153
+ async with NotionPageClient(token=token) as client:
154
+ page_response = await client.get_page(formatted_id)
155
+ return await cls._create_from_response(page_response, token)
156
+
157
+ except Exception as e:
158
+ cls.logger.error("Error creating page from URL '%s': %s", url, str(e))
159
+ raise
160
+
134
161
  @property
135
162
  def id(self) -> str:
136
163
  """
@@ -161,30 +188,30 @@ class NotionPage(LoggingMixin):
161
188
  return self._emoji_icon
162
189
 
163
190
  @property
164
- def properties(self) -> Optional[Dict[str, Any]]:
191
+ def properties(self) -> Optional[dict[str, Any]]:
165
192
  """
166
193
  Get the properties of the page.
167
194
  """
168
195
  return self._properties
169
196
 
170
197
  @property
171
- def block_registry(self) -> BlockRegistry:
172
- """
173
- Get the block element registry associated with this page.
198
+ def is_archived(self) -> bool:
199
+ return self._is_archived
174
200
 
175
- Returns:
176
- BlockElementRegistry: The registry of block elements.
177
- """
178
- return self._block_element_registry
201
+ @property
202
+ def is_in_trash(self) -> bool:
203
+ return self._is_in_trash
179
204
 
180
- def get_notion_markdown_system_prompt(self) -> str:
205
+ @property
206
+ def block_registry_builder(self) -> BlockRegistryBuilder:
181
207
  """
182
- Get the formatting prompt for the page content manager.
183
-
184
- Returns:
185
- str: The formatting prompt.
208
+ Returns the block registry builder for this page.
186
209
  """
187
- return self._block_element_registry.get_notion_markdown_syntax_prompt()
210
+ return self.block_element_registry.builder
211
+
212
+ def get_prompt_information(self) -> str:
213
+ markdown_syntax_builder = SyntaxPromptBuilder()
214
+ return markdown_syntax_builder.build_concise_reference()
188
215
 
189
216
  async def set_title(self, title: str) -> str:
190
217
  """
@@ -205,38 +232,65 @@ class NotionPage(LoggingMixin):
205
232
  except Exception as e:
206
233
  self.logger.error("Error setting page title: %s", str(e))
207
234
 
208
- async def append_markdown(self, markdown: str, append_divider=False) -> bool:
235
+ async def append_markdown(
236
+ self,
237
+ content: Union[str, Callable[[MarkdownBuilder], MarkdownBuilder]],
238
+ *,
239
+ prepend_table_of_contents: bool = False,
240
+ append_divider: bool = False,
241
+ ) -> bool:
209
242
  """
210
243
  Append markdown content to the page.
211
- """
212
- return await self._page_content_writer.append_markdown(
213
- markdown_text=markdown, append_divider=append_divider
214
- )
215
244
 
216
- async def clear_page_content(self) -> bool:
217
- """
218
- Clear all content from the page.
245
+ Args:
246
+ content: Either raw markdown text OR a callback function that receives a MarkdownBuilder
247
+ prepend_table_of_contents: Whether to prepend table of contents
248
+ append_divider: Whether to append a divider
249
+
250
+ Returns:
251
+ bool: True if successful, False otherwise
219
252
  """
220
- return await self._page_content_writer.clear_page_content()
253
+ result = await self._page_content_writer.append_markdown(
254
+ content=content,
255
+ append_divider=append_divider,
256
+ prepend_table_of_contents=prepend_table_of_contents,
257
+ )
258
+ return result is not None
221
259
 
222
- async def replace_content(self, markdown: str) -> bool:
260
+ async def replace_content(
261
+ self,
262
+ content: Union[str, Callable[[MarkdownBuilder], MarkdownBuilder]],
263
+ *,
264
+ prepend_table_of_contents: bool = False,
265
+ append_divider: bool = False,
266
+ ) -> bool:
223
267
  """
224
268
  Replace the entire page content with new markdown content.
225
269
 
226
270
  Args:
227
- markdown: The new markdown content.
271
+ content: Either raw markdown text OR a callback function that receives a MarkdownBuilder
272
+ prepend_table_of_contents: Whether to prepend table of contents
273
+ append_divider: Whether to append a divider
228
274
 
229
275
  Returns:
230
- str: Status or confirmation message.
276
+ bool: True if successful, False otherwise
231
277
  """
232
- clear_result = await self._page_content_writer.clear_page_content()
278
+ clear_result = await self._page_content_deleting_service.clear_page_content()
233
279
  if not clear_result:
234
280
  self.logger.error("Failed to clear page content before replacement")
235
- return False
236
281
 
237
- return await self._page_content_writer.append_markdown(
238
- markdown_text=markdown, append_divider=False
282
+ result = await self._page_content_writer.append_markdown(
283
+ content=content,
284
+ prepend_table_of_contents=prepend_table_of_contents,
285
+ append_divider=append_divider,
239
286
  )
287
+ return result is not None
288
+
289
+ async def clear_page_content(self) -> str:
290
+ """
291
+ Clear all content from the page.
292
+ """
293
+ return await self._page_content_deleting_service.clear_page_content()
240
294
 
241
295
  async def get_text_content(self) -> str:
242
296
  """
@@ -245,7 +299,10 @@ class NotionPage(LoggingMixin):
245
299
  Returns:
246
300
  str: The text content of the page.
247
301
  """
248
- return await self._page_content_retriever.get_page_content()
302
+ blocks = await self._block_client.get_blocks_by_page_id_recursively(
303
+ page_id=self._page_id
304
+ )
305
+ return await self._page_content_retriever.convert_to_markdown(blocks=blocks)
249
306
 
250
307
  async def set_emoji_icon(self, emoji: str) -> Optional[str]:
251
308
  """
@@ -263,7 +320,27 @@ class NotionPage(LoggingMixin):
263
320
 
264
321
  self.logger.error(f"Error updating page emoji: {str(e)}")
265
322
  return None
266
-
323
+
324
+ async def create_child_database(self, title: str) -> NotionDatabase:
325
+ from notionary import NotionDatabase
326
+ database_client = NotionDatabaseClient(token=self._client.token)
327
+
328
+ create_database_response = await database_client.create_database(
329
+ title=title,
330
+ parent_page_id=self._page_id,
331
+ )
332
+
333
+ return await NotionDatabase.from_database_id(id=create_database_response.id, token=self._client.token)
334
+
335
+ async def create_child_page(self, title: str) -> NotionPage:
336
+ from notionary import NotionPage
337
+ child_page_response = await self._client.create_page(
338
+ parent_page_id=self._page_id,
339
+ title=title,
340
+ )
341
+
342
+ return await NotionPage.from_page_id(page_id=child_page_response.id, token=self._client.token)
343
+
267
344
  async def set_external_icon(self, url: str) -> Optional[str]:
268
345
  """
269
346
  Sets the page icon to an external image.
@@ -385,7 +462,6 @@ class NotionPage(LoggingMixin):
385
462
  )
386
463
  return []
387
464
 
388
- # Diese Methode hier sollte auch für relation properties funktionieren aber gerne auch eine dedizierte hier
389
465
  async def set_property_value_by_name(self, property_name: str, value: Any) -> Any:
390
466
  """
391
467
  Set the value of a specific property by its name.
@@ -509,6 +585,8 @@ class NotionPage(LoggingMixin):
509
585
  title=title,
510
586
  url=page_response.url,
511
587
  emoji_icon=emoji,
588
+ archived=page_response.archived,
589
+ in_trash=page_response.in_trash,
512
590
  properties=page_response.properties,
513
591
  parent_database=parent_database,
514
592
  token=token,
@@ -558,4 +636,4 @@ class NotionPage(LoggingMixin):
558
636
  """Extract parent database ID from page response."""
559
637
  parent = page_response.parent
560
638
  if isinstance(parent, DatabaseParent):
561
- return parent.database_id
639
+ return parent.database_id