notionary 0.1.29__py3-none-any.whl → 0.2.0__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 (59) hide show
  1. notionary/__init__.py +5 -5
  2. notionary/database/notion_database.py +50 -59
  3. notionary/database/notion_database_factory.py +16 -20
  4. notionary/elements/audio_element.py +1 -1
  5. notionary/elements/bookmark_element.py +1 -1
  6. notionary/elements/bulleted_list_element.py +2 -8
  7. notionary/elements/callout_element.py +1 -1
  8. notionary/elements/code_block_element.py +1 -1
  9. notionary/elements/divider_element.py +1 -1
  10. notionary/elements/embed_element.py +1 -1
  11. notionary/elements/heading_element.py +2 -8
  12. notionary/elements/image_element.py +1 -1
  13. notionary/elements/mention_element.py +1 -1
  14. notionary/elements/notion_block_element.py +1 -1
  15. notionary/elements/numbered_list_element.py +2 -7
  16. notionary/elements/paragraph_element.py +1 -1
  17. notionary/elements/qoute_element.py +1 -1
  18. notionary/elements/registry/{block_element_registry.py → block_registry.py} +70 -26
  19. notionary/elements/registry/{block_element_registry_builder.py → block_registry_builder.py} +48 -32
  20. notionary/elements/table_element.py +1 -1
  21. notionary/elements/text_inline_formatter.py +13 -9
  22. notionary/elements/todo_element.py +1 -1
  23. notionary/elements/toggle_element.py +1 -1
  24. notionary/elements/toggleable_heading_element.py +1 -1
  25. notionary/elements/video_element.py +1 -1
  26. notionary/models/notion_block_response.py +264 -0
  27. notionary/models/notion_database_response.py +63 -0
  28. notionary/models/notion_page_response.py +100 -0
  29. notionary/notion_client.py +38 -5
  30. notionary/page/content/page_content_retriever.py +68 -0
  31. notionary/page/content/page_content_writer.py +103 -0
  32. notionary/page/markdown_to_notion_converter.py +5 -5
  33. notionary/page/metadata/metadata_editor.py +91 -63
  34. notionary/page/metadata/notion_icon_manager.py +55 -28
  35. notionary/page/metadata/notion_page_cover_manager.py +23 -20
  36. notionary/page/notion_page.py +223 -218
  37. notionary/page/notion_page_factory.py +102 -151
  38. notionary/page/notion_to_markdown_converter.py +5 -5
  39. notionary/page/properites/database_property_service.py +11 -55
  40. notionary/page/properites/page_property_manager.py +44 -67
  41. notionary/page/properites/property_value_extractor.py +3 -3
  42. notionary/page/relations/notion_page_relation_manager.py +165 -213
  43. notionary/page/relations/notion_page_title_resolver.py +59 -41
  44. notionary/page/relations/page_database_relation.py +7 -9
  45. notionary/{elements/prompts → prompting}/element_prompt_content.py +19 -4
  46. notionary/prompting/markdown_syntax_prompt_generator.py +92 -0
  47. notionary/util/logging_mixin.py +17 -8
  48. notionary/util/warn_direct_constructor_usage.py +54 -0
  49. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/METADATA +2 -1
  50. notionary-0.2.0.dist-info/RECORD +60 -0
  51. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/WHEEL +1 -1
  52. notionary/database/database_info_service.py +0 -43
  53. notionary/elements/prompts/synthax_prompt_builder.py +0 -150
  54. notionary/page/content/page_content_manager.py +0 -211
  55. notionary/page/properites/property_operation_result.py +0 -116
  56. notionary/page/relations/relation_operation_result.py +0 -144
  57. notionary-0.1.29.dist-info/RECORD +0 -58
  58. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/licenses/LICENSE +0 -0
  59. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
+ from __future__ import annotations
2
+ from typing import Any, Dict, List, Optional
1
3
  import re
2
- from typing import Any, Dict, List, Optional, Union
3
4
 
4
- from notionary.elements.registry.block_element_registry import BlockElementRegistry
5
- from notionary.elements.registry.block_element_registry_builder import (
6
- BlockElementRegistryBuilder,
7
- )
5
+ from notionary.elements.registry.block_registry import BlockRegistry
6
+ from notionary.elements.registry.block_registry_builder import BlockRegistryBuilder
8
7
  from notionary.notion_client import NotionClient
8
+ from notionary.page.content.page_content_retriever import PageContentRetriever
9
9
  from notionary.page.metadata.metadata_editor import MetadataEditor
10
10
  from notionary.page.metadata.notion_icon_manager import NotionPageIconManager
11
11
  from notionary.page.metadata.notion_page_cover_manager import (
@@ -15,10 +15,12 @@ from notionary.page.properites.database_property_service import (
15
15
  DatabasePropertyService,
16
16
  )
17
17
  from notionary.page.relations.notion_page_relation_manager import (
18
- NotionRelationManager,
18
+ NotionPageRelationManager,
19
19
  )
20
- from notionary.page.content.page_content_manager import PageContentManager
20
+ from notionary.page.content.page_content_writer import PageContentWriter
21
21
  from notionary.page.properites.page_property_manager import PagePropertyManager
22
+ from notionary.page.relations.notion_page_title_resolver import NotionPageTitleResolver
23
+ from notionary.util.warn_direct_constructor_usage import warn_direct_constructor_usage
22
24
  from notionary.util.logging_mixin import LoggingMixin
23
25
  from notionary.util.page_id_utils import extract_and_validate_page_id
24
26
  from notionary.page.relations.page_database_relation import PageDatabaseRelation
@@ -26,9 +28,10 @@ from notionary.page.relations.page_database_relation import PageDatabaseRelation
26
28
 
27
29
  class NotionPage(LoggingMixin):
28
30
  """
29
- High-Level Facade for managing content and metadata of a Notion page.
31
+ Managing content and metadata of a Notion page.
30
32
  """
31
33
 
34
+ @warn_direct_constructor_usage
32
35
  def __init__(
33
36
  self,
34
37
  page_id: Optional[str] = None,
@@ -44,15 +47,20 @@ class NotionPage(LoggingMixin):
44
47
  self._title_loaded = title is not None
45
48
  self._url_loaded = url is not None
46
49
 
47
- self._block_element_registry = (
48
- BlockElementRegistryBuilder.create_full_registry()
50
+ self._block_element_registry = BlockRegistryBuilder.create_full_registry()
51
+
52
+ self._page_content_writer = PageContentWriter(
53
+ page_id=self._page_id,
54
+ client=self._client,
55
+ block_registry=self._block_element_registry,
49
56
  )
50
57
 
51
- self._page_content_manager = PageContentManager(
58
+ self._page_content_retriever = PageContentRetriever(
52
59
  page_id=self._page_id,
53
60
  client=self._client,
54
61
  block_registry=self._block_element_registry,
55
62
  )
63
+
56
64
  self._metadata = MetadataEditor(self._page_id, self._client)
57
65
  self._page_cover_manager = NotionPageCoverManager(
58
66
  page_id=self._page_id, client=self._client
@@ -66,7 +74,7 @@ class NotionPage(LoggingMixin):
66
74
  )
67
75
  self._db_property_service = None
68
76
 
69
- self._relation_manager = NotionRelationManager(
77
+ self._relation_manager = NotionPageRelationManager(
70
78
  page_id=self._page_id, client=self._client
71
79
  )
72
80
 
@@ -74,18 +82,68 @@ class NotionPage(LoggingMixin):
74
82
  self._page_id, self._client, self._metadata, self._db_relation
75
83
  )
76
84
 
85
+ @classmethod
86
+ def from_page_id(cls, page_id: str, token: Optional[str] = None) -> NotionPage:
87
+ """
88
+ Create a NotionPage from a page ID.
89
+
90
+ Args:
91
+ page_id: The ID of the Notion page
92
+ token: Optional Notion API token (uses environment variable if not provided)
93
+
94
+ Returns:
95
+ An initialized NotionPage instance
96
+ """
97
+ from notionary.page.notion_page_factory import NotionPageFactory
98
+
99
+ cls.logger.info("Creating page from ID: %s", page_id)
100
+ return NotionPageFactory().from_page_id(page_id, token)
101
+
102
+ @classmethod
103
+ def from_url(cls, url: str, token: Optional[str] = None) -> NotionPage:
104
+ """
105
+ Create a NotionPage from a Notion URL.
106
+
107
+ Args:
108
+ url: The URL of the Notion page
109
+ token: Optional Notion API token (uses environment variable if not provided)
110
+
111
+ Returns:
112
+ An initialized NotionPage instance
113
+ """
114
+ from notionary.page.notion_page_factory import NotionPageFactory
115
+
116
+ cls.logger.info("Creating page from URL: %s", url)
117
+ return NotionPageFactory().from_url(url, token)
118
+
119
+ @classmethod
120
+ async def from_page_name(
121
+ cls, page_name: str, token: Optional[str] = None
122
+ ) -> NotionPage:
123
+ """
124
+ Create a NotionPage by finding a page with a matching name.
125
+ Uses fuzzy matching to find the closest match to the given name.
126
+
127
+ Args:
128
+ page_name: The name of the Notion page to search for
129
+ token: Optional Notion API token (uses environment variable if not provided)
130
+
131
+ Returns:
132
+ An initialized NotionPage instance
133
+ """
134
+ from notionary.page.notion_page_factory import NotionPageFactory
135
+
136
+ return await NotionPageFactory().from_page_name(page_name, token)
137
+
77
138
  @property
78
139
  def id(self) -> str:
79
140
  """
80
141
  Get the ID of the page.
81
-
82
- Returns:
83
- str: The page ID.
84
142
  """
85
143
  return self._page_id
86
144
 
87
145
  @property
88
- def block_registry(self) -> BlockElementRegistry:
146
+ def block_registry(self) -> BlockRegistry:
89
147
  """
90
148
  Get the block element registry associated with this page.
91
149
 
@@ -94,8 +152,18 @@ class NotionPage(LoggingMixin):
94
152
  """
95
153
  return self._block_element_registry
96
154
 
155
+ @property
156
+ def block_registry_builder(self) -> BlockRegistryBuilder:
157
+ """
158
+ Get the block element registry builder associated with this page.
159
+
160
+ Returns:
161
+ BlockElementRegistryBuilder: The builder for block elements.
162
+ """
163
+ return self._block_element_registry.builder
164
+
97
165
  @block_registry.setter
98
- def block_registry(self, block_registry: BlockElementRegistry) -> None:
166
+ def block_registry(self, block_registry: BlockRegistry) -> None:
99
167
  """
100
168
  Set the block element registry for the page content manager.
101
169
 
@@ -103,10 +171,22 @@ class NotionPage(LoggingMixin):
103
171
  block_registry: The registry of block elements to use.
104
172
  """
105
173
  self._block_element_registry = block_registry
106
- self._page_content_manager = PageContentManager(
174
+ self._page_content_writer = PageContentRetriever(
175
+ page_id=self._page_id, client=self._client, block_registry=block_registry
176
+ )
177
+ self._page_content_retriever = PageContentRetriever(
107
178
  page_id=self._page_id, client=self._client, block_registry=block_registry
108
179
  )
109
180
 
181
+ def get_notion_markdown_system_prompt(self) -> str:
182
+ """
183
+ Get the formatting prompt for the page content manager.
184
+
185
+ Returns:
186
+ str: The formatting prompt.
187
+ """
188
+ return self._block_element_registry.get_notion_markdown_syntax_prompt()
189
+
110
190
  async def get_title(self) -> str:
111
191
  """
112
192
  Get the title of the page, loading it if necessary.
@@ -115,9 +195,25 @@ class NotionPage(LoggingMixin):
115
195
  str: The page title.
116
196
  """
117
197
  if not self._title_loaded:
118
- await self._load_page_title()
198
+ self._title = await self._fetch_page_title()
119
199
  return self._title
120
200
 
201
+ async def set_title(self, title: str) -> Optional[Dict[str, Any]]:
202
+ """
203
+ Set the title of the page.
204
+
205
+ Args:
206
+ title: The new title.
207
+
208
+ Returns:
209
+ Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
210
+ """
211
+ result = await self._metadata.set_title(title)
212
+ if result:
213
+ self._title = title
214
+ self._title_loaded = True
215
+ return result
216
+
121
217
  async def get_url(self) -> str:
122
218
  """
123
219
  Get the URL of the page, constructing it if necessary.
@@ -126,11 +222,11 @@ class NotionPage(LoggingMixin):
126
222
  str: The page URL.
127
223
  """
128
224
  if not self._url_loaded:
129
- self._url = await self._build_notion_url()
225
+ self._url = await self._generate_url_from_title()
130
226
  self._url_loaded = True
131
227
  return self._url
132
228
 
133
- async def append_markdown(self, markdown: str, append_divider = False) -> str:
229
+ async def append_markdown(self, markdown: str, append_divider=False) -> bool:
134
230
  """
135
231
  Append markdown content to the page.
136
232
 
@@ -140,18 +236,20 @@ class NotionPage(LoggingMixin):
140
236
  Returns:
141
237
  str: Status or confirmation message.
142
238
  """
143
- return await self._page_content_manager.append_markdown(markdown_text=markdown, append_divider=append_divider)
239
+ return await self._page_content_writer.append_markdown(
240
+ markdown_text=markdown, append_divider=append_divider
241
+ )
144
242
 
145
- async def clear(self) -> str:
243
+ async def clear_page_content(self) -> bool:
146
244
  """
147
245
  Clear all content from the page.
148
246
 
149
247
  Returns:
150
248
  str: Status or confirmation message.
151
249
  """
152
- return await self._page_content_manager.clear()
250
+ return await self._page_content_writer.clear_page_content()
153
251
 
154
- async def replace_content(self, markdown: str) -> str:
252
+ async def replace_content(self, markdown: str) -> bool:
155
253
  """
156
254
  Replace the entire page content with new markdown content.
157
255
 
@@ -161,59 +259,58 @@ class NotionPage(LoggingMixin):
161
259
  Returns:
162
260
  str: Status or confirmation message.
163
261
  """
164
- await self._page_content_manager.clear()
165
- return await self._page_content_manager.append_markdown(markdown)
262
+ clear_result = await self._page_content_writer.clear_page_content()
263
+ if not clear_result:
264
+ self.logger.error("Failed to clear page content before replacement")
265
+ return False
266
+
267
+ return await self._page_content_writer.append_markdown(
268
+ markdown_text=markdown, append_divider=False
269
+ )
166
270
 
167
- async def get_text(self) -> str:
271
+ async def get_text_content(self) -> str:
168
272
  """
169
273
  Get the text content of the page.
170
274
 
171
275
  Returns:
172
276
  str: The text content of the page.
173
277
  """
174
- return await self._page_content_manager.get_text()
278
+ return await self._page_content_retriever.get_page_content()
175
279
 
176
- async def set_title(self, title: str) -> Optional[Dict[str, Any]]:
280
+ async def get_icon(self) -> str:
177
281
  """
178
- Set the title of the page.
179
-
180
- Args:
181
- title: The new title.
282
+ Retrieve the page icon - either emoji or external URL.
182
283
 
183
284
  Returns:
184
- Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
285
+ Optional[str]: The icon emoji or URL, or None if no icon is set.
185
286
  """
186
- result = await self._metadata.set_title(title)
187
- if result:
188
- self._title = title
189
- self._title_loaded = True
190
- return result
287
+ return await self._page_icon_manager.get_icon()
191
288
 
192
- async def set_page_icon(
193
- self, emoji: Optional[str] = None, external_url: Optional[str] = None
194
- ) -> Optional[Dict[str, Any]]:
289
+ async def set_emoji_icon(self, emoji: str) -> Optional[str]:
195
290
  """
196
- Set the icon for the page. Provide either emoji or external_url.
291
+ Sets the page icon to an emoji.
197
292
 
198
293
  Args:
199
- emoji: Optional emoji to use as icon.
200
- external_url: Optional URL to an external image to use as icon.
294
+ emoji (str): The emoji character to set as the icon.
201
295
 
202
296
  Returns:
203
297
  Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
204
298
  """
205
- return await self._page_icon_manager.set_icon(emoji, external_url)
299
+ return await self._page_icon_manager.set_emoji_icon(emoji=emoji)
206
300
 
207
- async def get_icon(self) -> str:
301
+ async def set_external_icon(self, url: str) -> Optional[str]:
208
302
  """
209
- Retrieve the page icon - either emoji or external URL.
303
+ Sets the page icon to an external image.
304
+
305
+ Args:
306
+ url (str): The URL of the external image to set as the icon.
210
307
 
211
308
  Returns:
212
- Optional[str]: The icon emoji or URL, or None if no icon is set.
309
+ Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
213
310
  """
214
- return await self._page_icon_manager.get_icon()
311
+ return await self._page_icon_manager.set_external_icon(external_icon_url=url)
215
312
 
216
- async def get_cover_url(self) -> str:
313
+ async def get_cover_url(self) -> Optional[str]:
217
314
  """
218
315
  Get the URL of the page cover image.
219
316
 
@@ -222,7 +319,7 @@ class NotionPage(LoggingMixin):
222
319
  """
223
320
  return await self._page_cover_manager.get_cover_url()
224
321
 
225
- async def set_page_cover(self, external_url: str) -> Optional[Dict[str, Any]]:
322
+ async def set_cover(self, external_url: str) -> Optional[str]:
226
323
  """
227
324
  Set the cover image for the page using an external URL.
228
325
 
@@ -243,16 +340,7 @@ class NotionPage(LoggingMixin):
243
340
  """
244
341
  return await self._page_cover_manager.set_random_gradient_cover()
245
342
 
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:
343
+ async def get_property_value_by_name(self, property_name: str) -> Any:
256
344
  """
257
345
  Get the value of a specific property.
258
346
 
@@ -262,126 +350,86 @@ class NotionPage(LoggingMixin):
262
350
  Returns:
263
351
  Any: The value of the property.
264
352
  """
265
- return await self._property_manager.get_property_value(
266
- property_name, self._relation_manager.get_relation_values
267
- )
353
+ properties = await self._property_manager._get_properties()
268
354
 
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.
355
+ if property_name not in properties:
356
+ return None
290
357
 
291
- Returns:
292
- bool: True if the page belongs to a database, False otherwise.
293
- """
294
- return await self._db_relation.is_database_page()
358
+ prop_data = properties[property_name]
359
+ prop_type = prop_data.get("type")
295
360
 
296
- async def get_parent_database_id(self) -> Optional[str]:
297
- """
298
- Get the ID of the database this page belongs to, if any.
361
+ if prop_type == "relation":
362
+ return await self._relation_manager.get_relation_values(property_name)
299
363
 
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()
364
+ return await self._property_manager.get_property_value(property_name)
304
365
 
305
- async def get_available_options_for_property(self, property_name: str) -> List[str]:
366
+ async def get_options_for_property(
367
+ self, property_name: str, limit: int = 100
368
+ ) -> List[str]:
306
369
  """
307
- Get the available option names for a property (select, multi_select, status).
370
+ Get the available options for a property (select, multi_select, status, relation).
308
371
 
309
372
  Args:
310
373
  property_name: The name of the property.
374
+ limit: Maximum number of options to return (only affects relation properties).
311
375
 
312
376
  Returns:
313
- List[str]: List of available option names.
377
+ List[str]: List of available option names or page titles.
314
378
  """
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 []
379
+ property_type = await self._get_property_type(property_name)
319
380
 
320
- async def get_property_type(self, property_name: str) -> Optional[str]:
321
- """
322
- Get the type of a specific property.
381
+ if property_type is None:
382
+ return []
323
383
 
324
- Args:
325
- property_name: The name of the property.
384
+ if property_type == "relation":
385
+ return await self._relation_manager.get_relation_options(
386
+ property_name, limit
387
+ )
326
388
 
327
- Returns:
328
- Optional[str]: The type of the property or None if not found.
329
- """
330
389
  db_service = await self._get_db_property_service()
331
390
  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.
391
+ return await db_service.get_option_names(property_name)
343
392
 
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": {}}
393
+ return []
351
394
 
352
- async def get_relation_options(
353
- self, property_name: str, limit: int = 100
354
- ) -> List[Dict[str, Any]]:
395
+ async def set_property_value_by_name(
396
+ self, property_name: str, value: Any
397
+ ) -> Optional[Dict[str, Any]]:
355
398
  """
356
- Return available options for a relation property.
399
+ Set the value of a specific property by its name.
357
400
 
358
401
  Args:
359
- property_name: The name of the relation property.
360
- limit: Maximum number of options to return.
402
+ property_name: The name of the property.
403
+ value: The new value to set.
361
404
 
362
405
  Returns:
363
- List[Dict[str, Any]]: List of available relation options.
406
+ Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
364
407
  """
365
- return await self._relation_manager.get_relation_options(property_name, limit)
408
+ return await self._property_manager.set_property_by_name(
409
+ property_name=property_name,
410
+ value=value,
411
+ )
366
412
 
367
- async def add_relations_by_name(
368
- self, relation_property_name: str, page_titles: Union[str, List[str]]
369
- ) -> Optional[Dict[str, Any]]:
413
+ async def set_relation_property_values_by_name(
414
+ self, relation_property_name: str, page_titles: List[str]
415
+ ) -> List[str]:
370
416
  """
371
417
  Add one or more relations to a relation property.
372
418
 
373
419
  Args:
374
420
  relation_property_name: The name of the relation property.
375
- page_titles: One or more page titles to relate to.
421
+ page_titles: A list of page titles to relate to.
376
422
 
377
423
  Returns:
378
424
  Optional[Dict[str, Any]]: Response data from the API if successful, None otherwise.
379
425
  """
380
- return await self._relation_manager.add_relation_by_name(
426
+ return await self._relation_manager.set_relation_values_by_page_titles(
381
427
  property_name=relation_property_name, page_titles=page_titles
382
428
  )
383
429
 
384
- async def get_relation_values(self, property_name: str) -> List[str]:
430
+ async def get_relation_property_values_by_name(
431
+ self, property_name: str
432
+ ) -> List[str]:
385
433
  """
386
434
  Return the current relation values for a property.
387
435
 
@@ -393,25 +441,7 @@ class NotionPage(LoggingMixin):
393
441
  """
394
442
  return await self._relation_manager.get_relation_values(property_name)
395
443
 
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:
444
+ async def get_last_edit_time(self) -> str:
415
445
  """
416
446
  Get the timestamp when the page was last edited.
417
447
 
@@ -419,78 +449,35 @@ class NotionPage(LoggingMixin):
419
449
  str: ISO 8601 formatted timestamp string of when the page was last edited.
420
450
  """
421
451
  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 ""
452
+ page_response = await self._client.get_page(self._page_id)
453
+ return (
454
+ page_response.last_edited_time if page_response.last_edited_by else ""
455
+ )
428
456
 
429
457
  except Exception as e:
430
458
  self.logger.error("Error retrieving last edited time: %s", str(e))
431
459
  return ""
432
460
 
433
- async def _load_page_title(self) -> str:
461
+ async def _fetch_page_title(self) -> str:
434
462
  """
435
463
  Load the page title from Notion API if not already loaded.
436
464
 
437
465
  Returns:
438
466
  str: The page title.
439
467
  """
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"
468
+ notion_page_title_resolver = NotionPageTitleResolver(self._client)
469
+ return await notion_page_title_resolver.get_title_by_page_id(
470
+ page_id=self._page_id
471
+ )
485
472
 
486
- async def _build_notion_url(self) -> str:
473
+ async def _generate_url_from_title(self) -> str:
487
474
  """
488
475
  Build a Notion URL from the page ID, including the title if available.
489
476
 
490
477
  Returns:
491
478
  str: The Notion URL for the page.
492
479
  """
493
- title = await self._load_page_title()
480
+ title = await self._fetch_page_title()
494
481
 
495
482
  url_title = ""
496
483
  if title and title != "Untitled":
@@ -520,3 +507,21 @@ class NotionPage(LoggingMixin):
520
507
  self._db_property_service = DatabasePropertyService(database_id, self._client)
521
508
  await self._db_property_service.load_schema()
522
509
  return self._db_property_service
510
+
511
+ async def _get_property_type(self, property_name: str) -> Optional[str]:
512
+ """
513
+ Get the type of a specific property.
514
+
515
+ Args:
516
+ property_name: The name of the property.
517
+
518
+ Returns:
519
+ Optional[str]: The type of the property, or None if not found.
520
+ """
521
+ properties = await self._property_manager._get_properties()
522
+
523
+ if property_name not in properties:
524
+ return None
525
+
526
+ prop_data = properties[property_name]
527
+ return prop_data.get("type")