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.
- notionary/__init__.py +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +18 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +65 -111
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +40 -66
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +52 -92
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +48 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +23 -95
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/models.py +0 -713
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
notionary/page/models.py
ADDED
@@ -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
|
notionary/page/notion_page.py
CHANGED
@@ -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
|
-
|
7
|
-
from notionary.
|
8
|
-
from notionary.
|
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.
|
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[
|
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.
|
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.
|
65
|
+
block_registry=self.block_element_registry,
|
59
66
|
)
|
60
67
|
|
61
|
-
self.
|
68
|
+
self._page_content_deleting_service = PageContentDeletingService(
|
62
69
|
page_id=self._page_id,
|
63
|
-
block_registry=self.
|
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[
|
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
|
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
|
-
|
176
|
-
|
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
|
-
|
205
|
+
@property
|
206
|
+
def block_registry_builder(self) -> BlockRegistryBuilder:
|
181
207
|
"""
|
182
|
-
|
183
|
-
|
184
|
-
Returns:
|
185
|
-
str: The formatting prompt.
|
208
|
+
Returns the block registry builder for this page.
|
186
209
|
"""
|
187
|
-
return self.
|
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(
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
276
|
+
bool: True if successful, False otherwise
|
231
277
|
"""
|
232
|
-
clear_result = await self.
|
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
|
-
|
238
|
-
|
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
|
-
|
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
|