notionary 0.2.18__py3-none-any.whl → 0.2.21__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 (204) 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 +263 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +42 -104
  7. notionary/blocks/audio/audio_markdown_node.py +3 -1
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +30 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +46 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
  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 +40 -55
  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 +40 -89
  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 +7 -0
  27. notionary/blocks/child_database/child_database_models.py +19 -0
  28. notionary/blocks/child_page/__init__.py +9 -0
  29. notionary/blocks/child_page/child_page_models.py +12 -0
  30. notionary/blocks/{shared/block_client.py → client.py} +55 -54
  31. notionary/blocks/code/__init__.py +6 -2
  32. notionary/blocks/code/code_element.py +53 -187
  33. notionary/blocks/code/code_markdown_node.py +13 -13
  34. notionary/blocks/code/code_models.py +94 -0
  35. notionary/blocks/column/__init__.py +25 -1
  36. notionary/blocks/column/column_element.py +40 -314
  37. notionary/blocks/column/column_list_element.py +37 -0
  38. notionary/blocks/column/column_list_markdown_node.py +50 -0
  39. notionary/blocks/column/column_markdown_node.py +59 -0
  40. notionary/blocks/column/column_models.py +26 -0
  41. notionary/blocks/divider/__init__.py +9 -2
  42. notionary/blocks/divider/divider_element.py +26 -49
  43. notionary/blocks/divider/divider_markdown_node.py +2 -1
  44. notionary/blocks/divider/divider_models.py +12 -0
  45. notionary/blocks/embed/__init__.py +9 -2
  46. notionary/blocks/embed/embed_element.py +47 -114
  47. notionary/blocks/embed/embed_markdown_node.py +3 -1
  48. notionary/blocks/embed/embed_models.py +14 -0
  49. notionary/blocks/equation/__init__.py +14 -0
  50. notionary/blocks/equation/equation_element.py +80 -0
  51. notionary/blocks/equation/equation_element_markdown_node.py +36 -0
  52. notionary/blocks/equation/equation_models.py +11 -0
  53. notionary/blocks/file/__init__.py +25 -0
  54. notionary/blocks/file/file_element.py +93 -0
  55. notionary/blocks/file/file_element_markdown_node.py +35 -0
  56. notionary/blocks/file/file_element_models.py +39 -0
  57. notionary/blocks/heading/__init__.py +16 -2
  58. notionary/blocks/heading/heading_element.py +67 -72
  59. notionary/blocks/heading/heading_markdown_node.py +2 -1
  60. notionary/blocks/heading/heading_models.py +29 -0
  61. notionary/blocks/image_block/__init__.py +13 -0
  62. notionary/blocks/image_block/image_element.py +84 -0
  63. notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
  64. notionary/blocks/image_block/image_models.py +10 -0
  65. notionary/blocks/models.py +172 -0
  66. notionary/blocks/numbered_list/__init__.py +12 -2
  67. notionary/blocks/numbered_list/numbered_list_element.py +33 -58
  68. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  69. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  70. notionary/blocks/paragraph/__init__.py +12 -2
  71. notionary/blocks/paragraph/paragraph_element.py +27 -69
  72. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  73. notionary/blocks/paragraph/paragraph_models.py +16 -0
  74. notionary/blocks/pdf/__init__.py +13 -0
  75. notionary/blocks/pdf/pdf_element.py +91 -0
  76. notionary/blocks/pdf/pdf_markdown_node.py +35 -0
  77. notionary/blocks/pdf/pdf_models.py +11 -0
  78. notionary/blocks/quote/__init__.py +11 -2
  79. notionary/blocks/quote/quote_element.py +31 -65
  80. notionary/blocks/quote/quote_markdown_node.py +4 -1
  81. notionary/blocks/quote/quote_models.py +18 -0
  82. notionary/blocks/registry/__init__.py +4 -0
  83. notionary/blocks/registry/block_registry.py +75 -91
  84. notionary/blocks/registry/block_registry_builder.py +107 -59
  85. notionary/blocks/rich_text/__init__.py +33 -0
  86. notionary/blocks/rich_text/rich_text_models.py +188 -0
  87. notionary/blocks/rich_text/text_inline_formatter.py +125 -0
  88. notionary/blocks/table/__init__.py +16 -2
  89. notionary/blocks/table/table_element.py +48 -241
  90. notionary/blocks/table/table_markdown_node.py +2 -1
  91. notionary/blocks/table/table_models.py +28 -0
  92. notionary/blocks/table_of_contents/__init__.py +19 -0
  93. notionary/blocks/table_of_contents/table_of_contents_element.py +51 -0
  94. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  95. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  96. notionary/blocks/todo/__init__.py +9 -2
  97. notionary/blocks/todo/todo_element.py +38 -95
  98. notionary/blocks/todo/todo_markdown_node.py +2 -1
  99. notionary/blocks/todo/todo_models.py +19 -0
  100. notionary/blocks/toggle/__init__.py +13 -3
  101. notionary/blocks/toggle/toggle_element.py +57 -264
  102. notionary/blocks/toggle/toggle_markdown_node.py +24 -14
  103. notionary/blocks/toggle/toggle_models.py +17 -0
  104. notionary/blocks/toggleable_heading/__init__.py +6 -2
  105. notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
  106. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  107. notionary/blocks/types.py +61 -0
  108. notionary/blocks/video/__init__.py +8 -2
  109. notionary/blocks/video/video_element.py +67 -143
  110. notionary/blocks/video/video_element_models.py +10 -0
  111. notionary/blocks/video/video_markdown_node.py +3 -1
  112. notionary/database/client.py +3 -8
  113. notionary/database/database.py +13 -14
  114. notionary/database/database_filter_builder.py +2 -2
  115. notionary/database/database_provider.py +5 -4
  116. notionary/database/models.py +337 -0
  117. notionary/database/notion_database.py +6 -7
  118. notionary/file_upload/client.py +5 -7
  119. notionary/file_upload/models.py +2 -1
  120. notionary/file_upload/notion_file_upload.py +2 -3
  121. notionary/markdown/markdown_builder.py +722 -0
  122. notionary/markdown/markdown_document_model.py +228 -0
  123. notionary/{blocks → markdown}/markdown_node.py +1 -0
  124. notionary/models/notion_database_response.py +0 -338
  125. notionary/page/client.py +9 -10
  126. notionary/page/models.py +327 -0
  127. notionary/page/notion_page.py +99 -52
  128. notionary/page/notion_text_length_utils.py +119 -0
  129. notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
  130. notionary/page/reader/handler/__init__.py +17 -0
  131. notionary/page/reader/handler/base_block_renderer.py +44 -0
  132. notionary/page/reader/handler/block_processing_context.py +35 -0
  133. notionary/page/reader/handler/block_rendering_context.py +43 -0
  134. notionary/page/reader/handler/column_list_renderer.py +51 -0
  135. notionary/page/reader/handler/column_renderer.py +60 -0
  136. notionary/page/reader/handler/line_renderer.py +60 -0
  137. notionary/page/reader/handler/toggle_renderer.py +69 -0
  138. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  139. notionary/page/reader/page_content_retriever.py +69 -0
  140. notionary/page/search_filter_builder.py +2 -1
  141. notionary/page/writer/handler/__init__.py +22 -0
  142. notionary/page/writer/handler/code_handler.py +100 -0
  143. notionary/page/writer/handler/column_handler.py +141 -0
  144. notionary/page/writer/handler/column_list_handler.py +139 -0
  145. notionary/page/writer/handler/line_handler.py +35 -0
  146. notionary/page/writer/handler/line_processing_context.py +54 -0
  147. notionary/page/writer/handler/regular_line_handler.py +92 -0
  148. notionary/page/writer/handler/table_handler.py +130 -0
  149. notionary/page/writer/handler/toggle_handler.py +153 -0
  150. notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
  151. notionary/page/writer/markdown_to_notion_converter.py +76 -0
  152. notionary/telemetry/__init__.py +2 -2
  153. notionary/telemetry/service.py +4 -3
  154. notionary/user/__init__.py +2 -2
  155. notionary/user/base_notion_user.py +2 -1
  156. notionary/user/client.py +2 -3
  157. notionary/user/models.py +1 -0
  158. notionary/user/notion_bot_user.py +4 -5
  159. notionary/user/notion_user.py +3 -4
  160. notionary/user/notion_user_manager.py +3 -2
  161. notionary/user/notion_user_provider.py +1 -1
  162. notionary/util/__init__.py +3 -2
  163. notionary/util/fuzzy.py +2 -1
  164. notionary/util/logging_mixin.py +2 -2
  165. notionary/util/singleton_metaclass.py +1 -1
  166. notionary/workspace.py +3 -2
  167. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
  168. notionary-0.2.21.dist-info/RECORD +185 -0
  169. notionary/blocks/document/__init__.py +0 -7
  170. notionary/blocks/document/document_element.py +0 -102
  171. notionary/blocks/document/document_markdown_node.py +0 -31
  172. notionary/blocks/image/__init__.py +0 -7
  173. notionary/blocks/image/image_element.py +0 -151
  174. notionary/blocks/markdown_builder.py +0 -356
  175. notionary/blocks/mention/__init__.py +0 -7
  176. notionary/blocks/mention/mention_element.py +0 -229
  177. notionary/blocks/mention/mention_markdown_node.py +0 -38
  178. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  179. notionary/blocks/prompts/element_prompt_content.py +0 -41
  180. notionary/blocks/shared/__init__.py +0 -0
  181. notionary/blocks/shared/models.py +0 -710
  182. notionary/blocks/shared/notion_block_element.py +0 -37
  183. notionary/blocks/shared/text_inline_formatter.py +0 -262
  184. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  185. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  186. notionary/database/models/page_result.py +0 -10
  187. notionary/models/notion_block_response.py +0 -264
  188. notionary/models/notion_page_response.py +0 -78
  189. notionary/models/search_response.py +0 -0
  190. notionary/page/__init__.py +0 -0
  191. notionary/page/content/notion_text_length_utils.py +0 -87
  192. notionary/page/content/page_content_retriever.py +0 -52
  193. notionary/page/formatting/line_processor.py +0 -153
  194. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  195. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  196. notionary/page/notion_to_markdown_converter.py +0 -179
  197. notionary/page/properites/property_value_extractor.py +0 -0
  198. notionary-0.2.18.dist-info/RECORD +0 -149
  199. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  200. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  201. /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
  202. /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
  203. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
  204. {notionary-0.2.18.dist-info → notionary-0.2.21.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,23 +1,23 @@
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
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union
5
6
 
6
- from notionary.blocks import BlockRegistry
7
- from notionary.models.notion_database_response import NotionPageResponse
8
- from notionary.models.notion_page_response import DatabaseParent
7
+ from notionary.blocks.client import NotionBlockClient
8
+ from notionary.blocks.models import DatabaseParent
9
+ from notionary.blocks.registry.block_registry import BlockRegistry
10
+ from notionary.blocks.registry.block_registry_builder import BlockRegistryBuilder
11
+ from notionary.markdown.markdown_builder import MarkdownBuilder
9
12
  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
13
+ from notionary.page.models import NotionPageResponse
14
+ from notionary.page.page_content_writer import PageContentWriter
14
15
  from notionary.page.property_formatter import NotionPropertyFormatter
16
+ from notionary.page.reader.page_content_retriever import PageContentRetriever
15
17
  from notionary.page.utils import extract_property_value
16
-
17
- from notionary.util import LoggingMixin, format_uuid, factory_only
18
+ from notionary.util import LoggingMixin, extract_uuid, factory_only, format_uuid
18
19
  from notionary.util.fuzzy import find_best_match
19
20
 
20
-
21
21
  if TYPE_CHECKING:
22
22
  from notionary import NotionDatabase
23
23
 
@@ -27,14 +27,16 @@ class NotionPage(LoggingMixin):
27
27
  Managing content and metadata of a Notion page.
28
28
  """
29
29
 
30
- @factory_only("from_page_id", "from_page_name")
30
+ @factory_only("from_page_id", "from_page_name", "from_url")
31
31
  def __init__(
32
32
  self,
33
33
  page_id: str,
34
34
  title: str,
35
35
  url: str,
36
+ archived: bool,
37
+ in_trash: bool,
36
38
  emoji_icon: Optional[str] = None,
37
- properties: Optional[Dict[str, Any]] = None,
39
+ properties: Optional[dict[str, Any]] = None,
38
40
  parent_database: Optional[NotionDatabase] = None,
39
41
  token: Optional[str] = None,
40
42
  ):
@@ -44,23 +46,25 @@ class NotionPage(LoggingMixin):
44
46
  self._page_id = page_id
45
47
  self._title = title
46
48
  self._url = url
49
+ self._is_archived = archived
50
+ self._is_in_trash = in_trash
47
51
  self._emoji_icon = emoji_icon
48
52
  self._properties = properties
49
53
  self._parent_database = parent_database
50
54
 
51
55
  self._client = NotionPageClient(token=token)
56
+ self._block_client = NotionBlockClient(token=token)
52
57
  self._page_data = None
53
58
 
54
- self._block_element_registry = BlockRegistry.create_registry()
59
+ self.block_element_registry = BlockRegistry.create_registry()
55
60
 
56
61
  self._page_content_writer = PageContentWriter(
57
62
  page_id=self._page_id,
58
- block_registry=self._block_element_registry,
63
+ block_registry=self.block_element_registry,
59
64
  )
60
65
 
61
66
  self._page_content_retriever = PageContentRetriever(
62
- page_id=self._page_id,
63
- block_registry=self._block_element_registry,
67
+ block_registry=self.block_element_registry,
64
68
  )
65
69
 
66
70
  @classmethod
@@ -69,10 +73,6 @@ class NotionPage(LoggingMixin):
69
73
  ) -> NotionPage:
70
74
  """
71
75
  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
76
  """
77
77
  formatted_id = format_uuid(page_id) or page_id
78
78
 
@@ -131,6 +131,26 @@ class NotionPage(LoggingMixin):
131
131
  cls.logger.error("Error finding page by name: %s", str(e))
132
132
  raise
133
133
 
134
+ @classmethod
135
+ async def from_url(cls, url: str, token: Optional[str] = None) -> NotionPage:
136
+ """
137
+ Create a NotionPage from a Notion page URL.
138
+ """
139
+ try:
140
+ page_id = extract_uuid(url)
141
+ if not page_id:
142
+ raise ValueError(f"Could not extract page ID from URL: {url}")
143
+
144
+ formatted_id = format_uuid(page_id) or page_id
145
+
146
+ async with NotionPageClient(token=token) as client:
147
+ page_response = await client.get_page(formatted_id)
148
+ return await cls._create_from_response(page_response, token)
149
+
150
+ except Exception as e:
151
+ cls.logger.error("Error creating page from URL '%s': %s", url, str(e))
152
+ raise
153
+
134
154
  @property
135
155
  def id(self) -> str:
136
156
  """
@@ -161,30 +181,26 @@ class NotionPage(LoggingMixin):
161
181
  return self._emoji_icon
162
182
 
163
183
  @property
164
- def properties(self) -> Optional[Dict[str, Any]]:
184
+ def properties(self) -> Optional[dict[str, Any]]:
165
185
  """
166
186
  Get the properties of the page.
167
187
  """
168
188
  return self._properties
169
189
 
170
190
  @property
171
- def block_registry(self) -> BlockRegistry:
172
- """
173
- Get the block element registry associated with this page.
191
+ def is_archived(self) -> bool:
192
+ return self._is_archived
174
193
 
175
- Returns:
176
- BlockElementRegistry: The registry of block elements.
177
- """
178
- return self._block_element_registry
194
+ @property
195
+ def is_in_trash(self) -> bool:
196
+ return self._is_in_trash
179
197
 
180
- def get_notion_markdown_system_prompt(self) -> str:
198
+ @property
199
+ def block_registry_builder(self) -> BlockRegistryBuilder:
181
200
  """
182
- Get the formatting prompt for the page content manager.
183
-
184
- Returns:
185
- str: The formatting prompt.
201
+ Returns the block registry builder for this page.
186
202
  """
187
- return self._block_element_registry.get_notion_markdown_syntax_prompt()
203
+ return self.block_element_registry.builder
188
204
 
189
205
  async def set_title(self, title: str) -> str:
190
206
  """
@@ -205,38 +221,65 @@ class NotionPage(LoggingMixin):
205
221
  except Exception as e:
206
222
  self.logger.error("Error setting page title: %s", str(e))
207
223
 
208
- async def append_markdown(self, markdown: str, append_divider=False) -> bool:
224
+ async def append_markdown(
225
+ self,
226
+ content: Union[str, Callable[[MarkdownBuilder], MarkdownBuilder]],
227
+ *,
228
+ prepend_table_of_contents: bool = False,
229
+ append_divider: bool = False,
230
+ ) -> bool:
209
231
  """
210
232
  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
233
 
216
- async def clear_page_content(self) -> bool:
217
- """
218
- Clear all content from the page.
234
+ Args:
235
+ content: Either raw markdown text OR a callback function that receives a MarkdownBuilder
236
+ prepend_table_of_contents: Whether to prepend table of contents
237
+ append_divider: Whether to append a divider
238
+
239
+ Returns:
240
+ bool: True if successful, False otherwise
219
241
  """
220
- return await self._page_content_writer.clear_page_content()
242
+ result = await self._page_content_writer.append_markdown(
243
+ content=content,
244
+ append_divider=append_divider,
245
+ prepend_table_of_contents=prepend_table_of_contents,
246
+ )
247
+ return result is not None
221
248
 
222
- async def replace_content(self, markdown: str) -> bool:
249
+ async def replace_content(
250
+ self,
251
+ content: Union[str, Callable[[MarkdownBuilder], MarkdownBuilder]],
252
+ *,
253
+ prepend_table_of_contents: bool = False,
254
+ append_divider: bool = False,
255
+ ) -> bool:
223
256
  """
224
257
  Replace the entire page content with new markdown content.
225
258
 
226
259
  Args:
227
- markdown: The new markdown content.
260
+ content: Either raw markdown text OR a callback function that receives a MarkdownBuilder
261
+ prepend_table_of_contents: Whether to prepend table of contents
262
+ append_divider: Whether to append a divider
228
263
 
229
264
  Returns:
230
- str: Status or confirmation message.
265
+ bool: True if successful, False otherwise
231
266
  """
232
267
  clear_result = await self._page_content_writer.clear_page_content()
233
268
  if not clear_result:
234
269
  self.logger.error("Failed to clear page content before replacement")
235
- return False
236
270
 
237
- return await self._page_content_writer.append_markdown(
238
- markdown_text=markdown, append_divider=False
271
+ result = await self._page_content_writer.append_markdown(
272
+ content=content,
273
+ prepend_table_of_contents=prepend_table_of_contents,
274
+ append_divider=append_divider,
239
275
  )
276
+ return result is not None
277
+
278
+ async def clear_page_content(self) -> str:
279
+ """
280
+ Clear all content from the page.
281
+ """
282
+ return await self._page_content_writer.clear_page_content()
240
283
 
241
284
  async def get_text_content(self) -> str:
242
285
  """
@@ -245,7 +288,10 @@ class NotionPage(LoggingMixin):
245
288
  Returns:
246
289
  str: The text content of the page.
247
290
  """
248
- return await self._page_content_retriever.get_page_content()
291
+ blocks = await self._block_client.get_blocks_by_page_id_recursively(
292
+ page_id=self._page_id
293
+ )
294
+ return await self._page_content_retriever.convert_to_markdown(blocks=blocks)
249
295
 
250
296
  async def set_emoji_icon(self, emoji: str) -> Optional[str]:
251
297
  """
@@ -385,7 +431,6 @@ class NotionPage(LoggingMixin):
385
431
  )
386
432
  return []
387
433
 
388
- # Diese Methode hier sollte auch für relation properties funktionieren aber gerne auch eine dedizierte hier
389
434
  async def set_property_value_by_name(self, property_name: str, value: Any) -> Any:
390
435
  """
391
436
  Set the value of a specific property by its name.
@@ -509,6 +554,8 @@ class NotionPage(LoggingMixin):
509
554
  title=title,
510
555
  url=page_response.url,
511
556
  emoji_icon=emoji,
557
+ archived=page_response.archived,
558
+ in_trash=page_response.in_trash,
512
559
  properties=page_response.properties,
513
560
  parent_database=parent_database,
514
561
  token=token,