notionary 0.1.12__py3-none-any.whl → 0.1.14__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 +14 -10
- notionary/{core/database → database}/database_discovery.py +19 -17
- notionary/{core/database → database}/database_info_service.py +1 -1
- notionary/{core/database/notion_database_manager.py → database/notion_database.py} +13 -14
- notionary/{core/database/notion_database_manager_factory.py → database/notion_database_factory.py} +8 -12
- notionary/{core/converters/elements → elements}/audio_element.py +1 -1
- notionary/{core/converters/registry → elements}/block_element_registry.py +2 -5
- notionary/elements/block_element_registry_builder.py +401 -0
- notionary/{core/converters/elements → elements}/bookmark_element.py +1 -1
- notionary/{core/converters/elements → elements}/callout_element.py +2 -2
- notionary/{core/converters/elements → elements}/code_block_element.py +1 -1
- notionary/{core/converters/elements → elements}/column_element.py +1 -1
- notionary/{core/converters/elements → elements}/divider_element.py +1 -1
- notionary/{core/converters/elements → elements}/embed_element.py +1 -1
- notionary/{core/converters/elements → elements}/heading_element.py +2 -2
- notionary/{core/converters/elements → elements}/image_element.py +1 -1
- notionary/{core/converters/elements → elements}/list_element.py +2 -2
- notionary/elements/mention_element.py +135 -0
- notionary/{core/converters/elements → elements}/paragraph_element.py +2 -2
- notionary/{core/converters/elements → elements}/qoute_element.py +1 -1
- notionary/{core/converters/elements → elements}/table_element.py +2 -2
- notionary/{core/converters/elements → elements}/todo_lists.py +2 -2
- notionary/{core/converters/elements → elements}/toggle_element.py +1 -6
- notionary/{core/converters/elements → elements}/video_element.py +1 -1
- notionary/{core/notion_client.py → notion_client.py} +0 -1
- notionary/{core/page → page}/content/page_content_manager.py +7 -8
- notionary/{core/converters → page}/markdown_to_notion_converter.py +2 -4
- notionary/{core/page → page}/metadata/metadata_editor.py +2 -2
- notionary/{core/page → page}/metadata/notion_icon_manager.py +1 -1
- notionary/{core/page → page}/metadata/notion_page_cover_manager.py +1 -1
- notionary/page/notion_page.py +522 -0
- notionary/page/notion_page_factory.py +242 -0
- notionary/page/notion_to_markdown_converter.py +245 -0
- notionary/{core/page → page}/properites/database_property_service.py +1 -1
- notionary/{core/page → page}/properites/page_property_manager.py +7 -7
- notionary/{core/page → page}/relations/notion_page_relation_manager.py +3 -3
- notionary/{core/page → page}/relations/notion_page_title_resolver.py +1 -1
- notionary/{core/page → page}/relations/page_database_relation.py +1 -1
- notionary/util/page_id_utils.py +3 -1
- {notionary-0.1.12.dist-info → notionary-0.1.14.dist-info}/METADATA +1 -1
- notionary-0.1.14.dist-info/RECORD +56 -0
- notionary/core/converters/__init__.py +0 -50
- notionary/core/converters/notion_to_markdown_converter.py +0 -45
- notionary/core/converters/registry/block_element_registry_builder.py +0 -284
- notionary/core/page/notion_page_manager.py +0 -312
- notionary-0.1.12.dist-info/RECORD +0 -55
- /notionary/{core/database → database}/models/page_result.py +0 -0
- /notionary/{core/converters/elements → elements}/notion_block_element.py +0 -0
- /notionary/{core/converters/elements → elements}/text_inline_formatter.py +0 -0
- /notionary/{core/page → page}/content/notion_page_content_chunker.py +0 -0
- /notionary/{core/page → page}/properites/property_formatter.py +0 -0
- /notionary/{core/page → page}/properites/property_operation_result.py +0 -0
- /notionary/{core/page → page}/properites/property_value_extractor.py +0 -0
- /notionary/{core/page → page}/relations/relation_operation_result.py +0 -0
- {notionary-0.1.12.dist-info → notionary-0.1.14.dist-info}/WHEEL +0 -0
- {notionary-0.1.12.dist-info → notionary-0.1.14.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.1.12.dist-info → notionary-0.1.14.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
3
|
|
4
|
-
from notionary.
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
|
6
6
|
|
7
7
|
class ToggleElement(NotionBlockElement):
|
@@ -171,17 +171,12 @@ class ToggleElement(NotionBlockElement):
|
|
171
171
|
i += 1
|
172
172
|
continue
|
173
173
|
|
174
|
-
# Wenn context_aware aktiviert ist, prüfen wir für "Transcript"-Toggles
|
175
|
-
# ob sie direkt nach einem Bullet Point kommen
|
176
174
|
is_transcript_toggle = cls.TRANSCRIPT_TOGGLE_PATTERN.match(line.strip())
|
177
175
|
|
178
176
|
if context_aware and is_transcript_toggle:
|
179
|
-
# Prüfen, ob der Toggle in einem gültigen Kontext ist (nach Bullet Point)
|
180
177
|
if i > 0 and lines[i - 1].strip().startswith("- "):
|
181
|
-
# Gültiger Kontext, fahre fort
|
182
178
|
pass
|
183
179
|
else:
|
184
|
-
# Ungültiger Kontext für Transcript-Toggle, überspringe ihn
|
185
180
|
i += 1
|
186
181
|
continue
|
187
182
|
|
@@ -1,17 +1,16 @@
|
|
1
|
-
import
|
1
|
+
import json
|
2
2
|
from typing import Any, Dict, List, Optional
|
3
3
|
|
4
|
-
from notionary.
|
4
|
+
from notionary.elements.block_element_registry import BlockElementRegistry
|
5
|
+
from notionary.notion_client import NotionClient
|
6
|
+
|
7
|
+
from notionary.page.markdown_to_notion_converter import (
|
5
8
|
MarkdownToNotionConverter,
|
6
9
|
)
|
7
|
-
from notionary.
|
10
|
+
from notionary.page.notion_to_markdown_converter import (
|
8
11
|
NotionToMarkdownConverter,
|
9
12
|
)
|
10
|
-
from notionary.
|
11
|
-
BlockElementRegistry,
|
12
|
-
)
|
13
|
-
from notionary.core.notion_client import NotionClient
|
14
|
-
from notionary.core.page.content.notion_page_content_chunker import (
|
13
|
+
from notionary.page.content.notion_page_content_chunker import (
|
15
14
|
NotionPageContentChunker,
|
16
15
|
)
|
17
16
|
from notionary.util.logging_mixin import LoggingMixin
|
@@ -1,9 +1,7 @@
|
|
1
1
|
from typing import Dict, Any, List, Optional, Tuple
|
2
2
|
|
3
|
-
from notionary.
|
4
|
-
|
5
|
-
)
|
6
|
-
from notionary.core.converters.registry.block_element_registry_builder import (
|
3
|
+
from notionary.elements.block_element_registry import BlockElementRegistry
|
4
|
+
from notionary.elements.block_element_registry_builder import (
|
7
5
|
BlockElementRegistryBuilder,
|
8
6
|
)
|
9
7
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Any, Dict, Optional
|
2
|
-
from notionary.
|
3
|
-
from notionary.
|
2
|
+
from notionary.notion_client import NotionClient
|
3
|
+
from notionary.page.properites.property_formatter import NotionPropertyFormatter
|
4
4
|
from notionary.util.logging_mixin import LoggingMixin
|
5
5
|
|
6
6
|
|
@@ -0,0 +1,522 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
3
|
+
|
4
|
+
from notionary.elements.block_element_registry import BlockElementRegistry
|
5
|
+
from notionary.elements.block_element_registry_builder import (
|
6
|
+
BlockElementRegistryBuilder,
|
7
|
+
)
|
8
|
+
from notionary.notion_client import NotionClient
|
9
|
+
from notionary.page.metadata.metadata_editor import MetadataEditor
|
10
|
+
from notionary.page.metadata.notion_icon_manager import NotionPageIconManager
|
11
|
+
from notionary.page.metadata.notion_page_cover_manager import (
|
12
|
+
NotionPageCoverManager,
|
13
|
+
)
|
14
|
+
from notionary.page.properites.database_property_service import (
|
15
|
+
DatabasePropertyService,
|
16
|
+
)
|
17
|
+
from notionary.page.relations.notion_page_relation_manager import (
|
18
|
+
NotionRelationManager,
|
19
|
+
)
|
20
|
+
from notionary.page.content.page_content_manager import PageContentManager
|
21
|
+
from notionary.page.properites.page_property_manager import PagePropertyManager
|
22
|
+
from notionary.util.logging_mixin import LoggingMixin
|
23
|
+
from notionary.util.page_id_utils import extract_and_validate_page_id
|
24
|
+
from notionary.page.relations.page_database_relation import PageDatabaseRelation
|
25
|
+
|
26
|
+
|
27
|
+
class NotionPage(LoggingMixin):
|
28
|
+
"""
|
29
|
+
High-Level Facade for managing content and metadata of a Notion page.
|
30
|
+
"""
|
31
|
+
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
page_id: Optional[str] = None,
|
35
|
+
title: Optional[str] = None,
|
36
|
+
url: Optional[str] = None,
|
37
|
+
token: Optional[str] = None,
|
38
|
+
):
|
39
|
+
self._page_id = extract_and_validate_page_id(page_id=page_id, url=url)
|
40
|
+
self._url = url
|
41
|
+
self._title = title
|
42
|
+
self._client = NotionClient(token=token)
|
43
|
+
self._page_data = None
|
44
|
+
self._title_loaded = title is not None
|
45
|
+
self._url_loaded = url is not None
|
46
|
+
|
47
|
+
self._block_element_registry = (
|
48
|
+
BlockElementRegistryBuilder.create_standard_registry()
|
49
|
+
)
|
50
|
+
|
51
|
+
self._page_content_manager = PageContentManager(
|
52
|
+
page_id=self._page_id,
|
53
|
+
client=self._client,
|
54
|
+
block_registry=self._block_element_registry,
|
55
|
+
)
|
56
|
+
self._metadata = MetadataEditor(self._page_id, self._client)
|
57
|
+
self._page_cover_manager = NotionPageCoverManager(
|
58
|
+
page_id=self._page_id, client=self._client
|
59
|
+
)
|
60
|
+
self._page_icon_manager = NotionPageIconManager(
|
61
|
+
page_id=self._page_id, client=self._client
|
62
|
+
)
|
63
|
+
|
64
|
+
self._db_relation = PageDatabaseRelation(
|
65
|
+
page_id=self._page_id, client=self._client
|
66
|
+
)
|
67
|
+
self._db_property_service = None
|
68
|
+
|
69
|
+
self._relation_manager = NotionRelationManager(
|
70
|
+
page_id=self._page_id, client=self._client
|
71
|
+
)
|
72
|
+
|
73
|
+
self._property_manager = PagePropertyManager(
|
74
|
+
self._page_id, self._client, self._metadata, self._db_relation
|
75
|
+
)
|
76
|
+
|
77
|
+
@property
|
78
|
+
def id(self) -> str:
|
79
|
+
"""
|
80
|
+
Get the ID of the page.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
str: The page ID.
|
84
|
+
"""
|
85
|
+
return self._page_id
|
86
|
+
|
87
|
+
@property
|
88
|
+
def block_registry(self) -> BlockElementRegistry:
|
89
|
+
"""
|
90
|
+
Get the block element registry associated with this page.
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
BlockElementRegistry: The registry of block elements.
|
94
|
+
"""
|
95
|
+
return self._block_element_registry
|
96
|
+
|
97
|
+
@block_registry.setter
|
98
|
+
def block_registry(self, block_registry: BlockElementRegistry) -> None:
|
99
|
+
"""
|
100
|
+
Set the block element registry for the page content manager.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
block_registry: The registry of block elements to use.
|
104
|
+
"""
|
105
|
+
self._block_element_registry = block_registry
|
106
|
+
self._page_content_manager = PageContentManager(
|
107
|
+
page_id=self._page_id, client=self._client, block_registry=block_registry
|
108
|
+
)
|
109
|
+
|
110
|
+
async def get_title(self) -> str:
|
111
|
+
"""
|
112
|
+
Get the title of the page, loading it if necessary.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
str: The page title.
|
116
|
+
"""
|
117
|
+
if not self._title_loaded:
|
118
|
+
await self._load_page_title()
|
119
|
+
return self._title
|
120
|
+
|
121
|
+
async def get_url(self) -> str:
|
122
|
+
"""
|
123
|
+
Get the URL of the page, constructing it if necessary.
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
str: The page URL.
|
127
|
+
"""
|
128
|
+
if not self._url_loaded:
|
129
|
+
self._url = await self._build_notion_url()
|
130
|
+
self._url_loaded = True
|
131
|
+
return self._url
|
132
|
+
|
133
|
+
async def append_markdown(self, markdown: str) -> str:
|
134
|
+
"""
|
135
|
+
Append markdown content to the page.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
markdown: The markdown content to append.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
str: Status or confirmation message.
|
142
|
+
"""
|
143
|
+
return await self._page_content_manager.append_markdown(markdown)
|
144
|
+
|
145
|
+
async def clear(self) -> str:
|
146
|
+
"""
|
147
|
+
Clear all content from the page.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
str: Status or confirmation message.
|
151
|
+
"""
|
152
|
+
return await self._page_content_manager.clear()
|
153
|
+
|
154
|
+
async def replace_content(self, markdown: str) -> str:
|
155
|
+
"""
|
156
|
+
Replace the entire page content with new markdown content.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
markdown: The new markdown content.
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
str: Status or confirmation message.
|
163
|
+
"""
|
164
|
+
await self._page_content_manager.clear()
|
165
|
+
return await self._page_content_manager.append_markdown(markdown)
|
166
|
+
|
167
|
+
async def get_text(self) -> str:
|
168
|
+
"""
|
169
|
+
Get the text content of the page.
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
str: The text content of the page.
|
173
|
+
"""
|
174
|
+
return await self._page_content_manager.get_text()
|
175
|
+
|
176
|
+
async def set_title(self, title: str) -> Optional[Dict[str, Any]]:
|
177
|
+
"""
|
178
|
+
Set the title of the page.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
title: The new title.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
185
|
+
"""
|
186
|
+
result = await self._metadata.set_title(title)
|
187
|
+
if result:
|
188
|
+
self._title = title
|
189
|
+
self._title_loaded = True
|
190
|
+
return result
|
191
|
+
|
192
|
+
async def set_page_icon(
|
193
|
+
self, emoji: Optional[str] = None, external_url: Optional[str] = None
|
194
|
+
) -> Optional[Dict[str, Any]]:
|
195
|
+
"""
|
196
|
+
Set the icon for the page. Provide either emoji or external_url.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
emoji: Optional emoji to use as icon.
|
200
|
+
external_url: Optional URL to an external image to use as icon.
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
204
|
+
"""
|
205
|
+
return await self._page_icon_manager.set_icon(emoji, external_url)
|
206
|
+
|
207
|
+
async def get_icon(self) -> Optional[str]:
|
208
|
+
"""
|
209
|
+
Retrieve the page icon - either emoji or external URL.
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
Optional[str]: The icon emoji or URL, or None if no icon is set.
|
213
|
+
"""
|
214
|
+
return await self._page_icon_manager.get_icon()
|
215
|
+
|
216
|
+
async def get_cover_url(self) -> str:
|
217
|
+
"""
|
218
|
+
Get the URL of the page cover image.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
str: The URL of the cover image or empty string if not available.
|
222
|
+
"""
|
223
|
+
return await self._page_cover_manager.get_cover_url()
|
224
|
+
|
225
|
+
async def set_page_cover(self, external_url: str) -> Optional[Dict[str, Any]]:
|
226
|
+
"""
|
227
|
+
Set the cover image for the page using an external URL.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
external_url: URL to the external image.
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
234
|
+
"""
|
235
|
+
return await self._page_cover_manager.set_cover(external_url)
|
236
|
+
|
237
|
+
async def set_random_gradient_cover(self) -> Optional[Dict[str, Any]]:
|
238
|
+
"""
|
239
|
+
Set a random gradient as the page cover.
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
243
|
+
"""
|
244
|
+
return await self._page_cover_manager.set_random_gradient_cover()
|
245
|
+
|
246
|
+
async def get_properties(self) -> Dict[str, Any]:
|
247
|
+
"""
|
248
|
+
Retrieve all properties of the page.
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
Dict[str, Any]: Dictionary of property names and their values.
|
252
|
+
"""
|
253
|
+
return await self._property_manager.get_properties()
|
254
|
+
|
255
|
+
async def get_property_value(self, property_name: str) -> Any:
|
256
|
+
"""
|
257
|
+
Get the value of a specific property.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
property_name: The name of the property.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
Any: The value of the property.
|
264
|
+
"""
|
265
|
+
return await self._property_manager.get_property_value(
|
266
|
+
property_name, self._relation_manager.get_relation_values
|
267
|
+
)
|
268
|
+
|
269
|
+
async def set_property_by_name(
|
270
|
+
self, property_name: str, value: Any
|
271
|
+
) -> Optional[Dict[str, Any]]:
|
272
|
+
"""
|
273
|
+
Set the value of a specific property by its name.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
property_name: The name of the property.
|
277
|
+
value: The new value to set.
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
281
|
+
"""
|
282
|
+
return await self._property_manager.set_property_by_name(
|
283
|
+
property_name=property_name,
|
284
|
+
value=value,
|
285
|
+
)
|
286
|
+
|
287
|
+
async def is_database_page(self) -> bool:
|
288
|
+
"""
|
289
|
+
Check if this page belongs to a database.
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
bool: True if the page belongs to a database, False otherwise.
|
293
|
+
"""
|
294
|
+
return await self._db_relation.is_database_page()
|
295
|
+
|
296
|
+
async def get_parent_database_id(self) -> Optional[str]:
|
297
|
+
"""
|
298
|
+
Get the ID of the database this page belongs to, if any.
|
299
|
+
|
300
|
+
Returns:
|
301
|
+
Optional[str]: The database ID or None if the page doesn't belong to a database.
|
302
|
+
"""
|
303
|
+
return await self._db_relation.get_parent_database_id()
|
304
|
+
|
305
|
+
async def get_available_options_for_property(self, property_name: str) -> List[str]:
|
306
|
+
"""
|
307
|
+
Get the available option names for a property (select, multi_select, status).
|
308
|
+
|
309
|
+
Args:
|
310
|
+
property_name: The name of the property.
|
311
|
+
|
312
|
+
Returns:
|
313
|
+
List[str]: List of available option names.
|
314
|
+
"""
|
315
|
+
db_service = await self._get_db_property_service()
|
316
|
+
if db_service:
|
317
|
+
return await db_service.get_option_names(property_name)
|
318
|
+
return []
|
319
|
+
|
320
|
+
async def get_property_type(self, property_name: str) -> Optional[str]:
|
321
|
+
"""
|
322
|
+
Get the type of a specific property.
|
323
|
+
|
324
|
+
Args:
|
325
|
+
property_name: The name of the property.
|
326
|
+
|
327
|
+
Returns:
|
328
|
+
Optional[str]: The type of the property or None if not found.
|
329
|
+
"""
|
330
|
+
db_service = await self._get_db_property_service()
|
331
|
+
if db_service:
|
332
|
+
return await db_service.get_property_type(property_name)
|
333
|
+
return None
|
334
|
+
|
335
|
+
async def get_database_metadata(
|
336
|
+
self, include_types: Optional[List[str]] = None
|
337
|
+
) -> Dict[str, Any]:
|
338
|
+
"""
|
339
|
+
Get complete metadata about the database this page belongs to.
|
340
|
+
|
341
|
+
Args:
|
342
|
+
include_types: Optional list of property types to include. If None, all properties are included.
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
Dict[str, Any]: Database metadata or empty dict if not a database page.
|
346
|
+
"""
|
347
|
+
db_service = await self._get_db_property_service()
|
348
|
+
if db_service:
|
349
|
+
return await db_service.get_database_metadata(include_types)
|
350
|
+
return {"properties": {}}
|
351
|
+
|
352
|
+
async def get_relation_options(
|
353
|
+
self, property_name: str, limit: int = 100
|
354
|
+
) -> List[Dict[str, Any]]:
|
355
|
+
"""
|
356
|
+
Return available options for a relation property.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
property_name: The name of the relation property.
|
360
|
+
limit: Maximum number of options to return.
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
List[Dict[str, Any]]: List of available relation options.
|
364
|
+
"""
|
365
|
+
return await self._relation_manager.get_relation_options(property_name, limit)
|
366
|
+
|
367
|
+
async def add_relations_by_name(
|
368
|
+
self, relation_property_name: str, page_titles: Union[str, List[str]]
|
369
|
+
) -> Optional[Dict[str, Any]]:
|
370
|
+
"""
|
371
|
+
Add one or more relations to a relation property.
|
372
|
+
|
373
|
+
Args:
|
374
|
+
relation_property_name: The name of the relation property.
|
375
|
+
page_titles: One or more page titles to relate to.
|
376
|
+
|
377
|
+
Returns:
|
378
|
+
Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
|
379
|
+
"""
|
380
|
+
return await self._relation_manager.add_relation_by_name(
|
381
|
+
property_name=relation_property_name, page_titles=page_titles
|
382
|
+
)
|
383
|
+
|
384
|
+
async def get_relation_values(self, property_name: str) -> List[str]:
|
385
|
+
"""
|
386
|
+
Return the current relation values for a property.
|
387
|
+
|
388
|
+
Args:
|
389
|
+
property_name: The name of the relation property.
|
390
|
+
|
391
|
+
Returns:
|
392
|
+
List[str]: List of relation values.
|
393
|
+
"""
|
394
|
+
return await self._relation_manager.get_relation_values(property_name)
|
395
|
+
|
396
|
+
async def get_relation_property_ids(self) -> List[str]:
|
397
|
+
"""
|
398
|
+
Return a list of all relation property names.
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
List[str]: List of relation property names.
|
402
|
+
"""
|
403
|
+
return await self._relation_manager.get_relation_property_ids()
|
404
|
+
|
405
|
+
async def get_all_relations(self) -> Dict[str, List[str]]:
|
406
|
+
"""
|
407
|
+
Return all relation properties and their values.
|
408
|
+
|
409
|
+
Returns:
|
410
|
+
Dict[str, List[str]]: Dictionary mapping relation property names to their values.
|
411
|
+
"""
|
412
|
+
return await self._relation_manager.get_all_relations()
|
413
|
+
|
414
|
+
async def get_last_edited_time(self) -> str:
|
415
|
+
"""
|
416
|
+
Get the timestamp when the page was last edited.
|
417
|
+
|
418
|
+
Returns:
|
419
|
+
str: ISO 8601 formatted timestamp string of when the page was last edited.
|
420
|
+
"""
|
421
|
+
try:
|
422
|
+
page_data = await self._client.get_page(self._page_id)
|
423
|
+
if "last_edited_time" in page_data:
|
424
|
+
return page_data["last_edited_time"]
|
425
|
+
|
426
|
+
self.logger.warning("last_edited_time not found in page data")
|
427
|
+
return ""
|
428
|
+
|
429
|
+
except Exception as e:
|
430
|
+
self.logger.error("Error retrieving last edited time: %s", str(e))
|
431
|
+
return ""
|
432
|
+
|
433
|
+
async def _load_page_title(self) -> str:
|
434
|
+
"""
|
435
|
+
Load the page title from Notion API if not already loaded.
|
436
|
+
|
437
|
+
Returns:
|
438
|
+
str: The page title.
|
439
|
+
"""
|
440
|
+
if self._title is not None:
|
441
|
+
return self._title
|
442
|
+
|
443
|
+
self.logger.debug("Lazy loading page title for page: %s", self._page_id)
|
444
|
+
try:
|
445
|
+
# Retrieve page data
|
446
|
+
page_data = await self._client.get(f"pages/{self._page_id}")
|
447
|
+
self._title = self._extract_title_from_page_data(page_data)
|
448
|
+
except Exception as e:
|
449
|
+
self.logger.error("Error loading page title: %s", str(e))
|
450
|
+
self._title = "Untitled"
|
451
|
+
|
452
|
+
self._title_loaded = True
|
453
|
+
self.logger.debug("Loaded page title: %s", self._title)
|
454
|
+
return self._title
|
455
|
+
|
456
|
+
def _extract_title_from_page_data(self, page_data: Dict[str, Any]) -> str:
|
457
|
+
"""
|
458
|
+
Extract title from page data.
|
459
|
+
|
460
|
+
Args:
|
461
|
+
page_data: The page data from Notion API
|
462
|
+
|
463
|
+
Returns:
|
464
|
+
str: The extracted title or "Untitled" if not found
|
465
|
+
"""
|
466
|
+
if "properties" not in page_data:
|
467
|
+
return "Untitled"
|
468
|
+
|
469
|
+
for prop_value in page_data["properties"].values():
|
470
|
+
if prop_value.get("type") != "title":
|
471
|
+
continue
|
472
|
+
|
473
|
+
title_array = prop_value.get("title", [])
|
474
|
+
if not title_array:
|
475
|
+
continue
|
476
|
+
|
477
|
+
text_parts = []
|
478
|
+
for text_obj in title_array:
|
479
|
+
if "plain_text" in text_obj:
|
480
|
+
text_parts.append(text_obj["plain_text"])
|
481
|
+
|
482
|
+
return "".join(text_parts) or "Untitled"
|
483
|
+
|
484
|
+
return "Untitled"
|
485
|
+
|
486
|
+
async def _build_notion_url(self) -> str:
|
487
|
+
"""
|
488
|
+
Build a Notion URL from the page ID, including the title if available.
|
489
|
+
|
490
|
+
Returns:
|
491
|
+
str: The Notion URL for the page.
|
492
|
+
"""
|
493
|
+
title = await self._load_page_title()
|
494
|
+
|
495
|
+
url_title = ""
|
496
|
+
if title and title != "Untitled":
|
497
|
+
url_title = re.sub(r"[^\w\s-]", "", title)
|
498
|
+
url_title = re.sub(r"[\s]+", "-", url_title)
|
499
|
+
url_title = f"{url_title}-"
|
500
|
+
|
501
|
+
clean_id = self._page_id.replace("-", "")
|
502
|
+
|
503
|
+
return f"https://www.notion.so/{url_title}{clean_id}"
|
504
|
+
|
505
|
+
async def _get_db_property_service(self) -> Optional[DatabasePropertyService]:
|
506
|
+
"""
|
507
|
+
Gets the database property service, initializing it if necessary.
|
508
|
+
This is a more intuitive way to work with the instance variable.
|
509
|
+
|
510
|
+
Returns:
|
511
|
+
Optional[DatabasePropertyService]: The database property service or None if not applicable
|
512
|
+
"""
|
513
|
+
if self._db_property_service is not None:
|
514
|
+
return self._db_property_service
|
515
|
+
|
516
|
+
database_id = await self._db_relation.get_parent_database_id()
|
517
|
+
if not database_id:
|
518
|
+
return None
|
519
|
+
|
520
|
+
self._db_property_service = DatabasePropertyService(database_id, self._client)
|
521
|
+
await self._db_property_service.load_schema()
|
522
|
+
return self._db_property_service
|