notionary 0.1.29__py3-none-any.whl → 0.2.1__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.1.dist-info}/METADATA +2 -1
  50. notionary-0.2.1.dist-info/RECORD +60 -0
  51. {notionary-0.1.29.dist-info → notionary-0.2.1.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.1.dist-info}/licenses/LICENSE +0 -0
  59. {notionary-0.1.29.dist-info → notionary-0.2.1.dist-info}/top_level.txt +0 -0
notionary/__init__.py CHANGED
@@ -7,9 +7,9 @@ from .database.database_discovery import DatabaseDiscovery
7
7
  from .page.notion_page import NotionPage
8
8
  from .page.notion_page_factory import NotionPageFactory
9
9
 
10
- from .elements.registry.block_element_registry import BlockElementRegistry
11
- from .elements.registry.block_element_registry_builder import (
12
- BlockElementRegistryBuilder,
10
+ from .elements.registry.block_registry import BlockRegistry
11
+ from .elements.registry.block_registry_builder import (
12
+ BlockRegistryBuilder,
13
13
  )
14
14
 
15
15
  __all__ = [
@@ -19,6 +19,6 @@ __all__ = [
19
19
  "DatabaseDiscovery",
20
20
  "NotionPage",
21
21
  "NotionPageFactory",
22
- "BlockElementRegistry",
23
- "BlockElementRegistryBuilder",
22
+ "BlockRegistry",
23
+ "BlockRegistryBuilder",
24
24
  ]
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+ import json
1
3
  from typing import Any, AsyncGenerator, Dict, List, Optional
2
4
 
3
5
  from notionary.notion_client import NotionClient
4
6
  from notionary.page.notion_page import NotionPage
7
+ from notionary.util.warn_direct_constructor_usage import warn_direct_constructor_usage
5
8
  from notionary.util.logging_mixin import LoggingMixin
6
9
  from notionary.util.page_id_utils import format_uuid
7
10
 
@@ -13,6 +16,7 @@ class NotionDatabase(LoggingMixin):
13
16
  for further page operations.
14
17
  """
15
18
 
19
+ @warn_direct_constructor_usage
16
20
  def __init__(self, database_id: str, token: Optional[str] = None):
17
21
  """
18
22
  Initialize the minimal database manager.
@@ -21,9 +25,33 @@ class NotionDatabase(LoggingMixin):
21
25
  database_id: ID of the Notion database
22
26
  token: Optional Notion API token
23
27
  """
24
- self.database_id = format_uuid(database_id) or database_id
28
+ self.database_id = database_id
25
29
  self._client = NotionClient(token=token)
26
30
 
31
+ @classmethod
32
+ async def from_database_id(
33
+ cls, database_id: str, token: Optional[str] = None
34
+ ) -> NotionDatabase:
35
+ """
36
+ Create a NotionDatabase from a database ID.
37
+ Delegates to NotionDatabaseFactory.
38
+ """
39
+ from notionary.database.notion_database_factory import NotionDatabaseFactory
40
+
41
+ return await NotionDatabaseFactory.from_database_id(database_id, token)
42
+
43
+ @classmethod
44
+ async def from_database_name(
45
+ cls, database_name: str, token: Optional[str] = None
46
+ ) -> NotionDatabase:
47
+ """
48
+ Create a NotionDatabase by finding a database with a matching name.
49
+ Delegates to NotionDatabaseFactory.
50
+ """
51
+ from notionary.database.notion_database_factory import NotionDatabaseFactory
52
+
53
+ return await NotionDatabaseFactory.from_database_name(database_name, token)
54
+
27
55
  async def create_blank_page(self) -> Optional[NotionPage]:
28
56
  """
29
57
  Create a new blank page in the database with minimal properties.
@@ -42,7 +70,9 @@ class NotionDatabase(LoggingMixin):
42
70
  "Created blank page %s in database %s", page_id, self.database_id
43
71
  )
44
72
 
45
- return NotionPage(page_id=page_id)
73
+ return NotionPage.from_page_id(
74
+ page_id=page_id, token=self._client.token
75
+ )
46
76
 
47
77
  self.logger.warning("Page creation failed: invalid response")
48
78
  return None
@@ -122,7 +152,6 @@ class NotionDatabase(LoggingMixin):
122
152
  start_cursor: Optional[str] = None
123
153
  has_more = True
124
154
 
125
- # Prepare the query body
126
155
  body: Dict[str, Any] = {"page_size": page_size}
127
156
 
128
157
  if filter_conditions:
@@ -145,46 +174,13 @@ class NotionDatabase(LoggingMixin):
145
174
 
146
175
  for page in result["results"]:
147
176
  page_id: str = page.get("id", "")
148
- title = self._extract_page_title(page)
149
177
 
150
- page_url = f"https://notion.so/{page_id.replace('-', '')}"
151
-
152
- notion_page_manager = NotionPage(
153
- page_id=page_id, title=title, url=page_url
154
- )
155
- yield notion_page_manager
178
+ yield NotionPage.from_page_id(page_id=page_id, token=self._client.token)
156
179
 
157
- # Update pagination parameters
158
180
  has_more = result.get("has_more", False)
159
181
  start_cursor = result.get("next_cursor") if has_more else None
160
182
 
161
- def _extract_page_title(self, page: Dict[str, Any]) -> str:
162
- """
163
- Extracts the title from a Notion page object.
164
-
165
- Args:
166
- page: The Notion page object
167
-
168
- Returns:
169
- The extracted title as a string, or an empty string if no title found
170
- """
171
- properties = page.get("properties", {})
172
- if not properties:
173
- return ""
174
-
175
- for prop_value in properties.values():
176
- if prop_value.get("type") != "title":
177
- continue
178
-
179
- title_array = prop_value.get("title", [])
180
- if not title_array:
181
- continue
182
-
183
- return title_array[0].get("plain_text", "")
184
-
185
- return ""
186
-
187
- async def delete_page(self, page_id: str) -> Dict[str, Any]:
183
+ async def archive_page(self, page_id: str) -> bool:
188
184
  """
189
185
  Delete (archive) a page.
190
186
 
@@ -192,51 +188,46 @@ class NotionDatabase(LoggingMixin):
192
188
  page_id: The ID of the page to delete
193
189
 
194
190
  Returns:
195
- Dict with success status, message, and page_id when successful
191
+ bool: True if successful, False otherwise
196
192
  """
197
193
  try:
198
- formatted_page_id = format_uuid(page_id) or page_id
194
+ formatted_page_id = format_uuid(page_id)
199
195
 
200
- # Archive the page (Notion's way of deleting)
201
196
  data = {"archived": True}
202
197
 
203
- result = await self._client.patch(f"pages/{formatted_page_id}", data)
198
+ result = await self._client.patch_page(formatted_page_id, data)
199
+
204
200
  if not result:
205
201
  self.logger.error("Error deleting page %s", formatted_page_id)
206
- return {
207
- "success": False,
208
- "message": f"Failed to delete page {formatted_page_id}",
209
- }
202
+ return False
210
203
 
211
204
  self.logger.info(
212
205
  "Page %s successfully deleted (archived)", formatted_page_id
213
206
  )
214
- return {"success": True, "page_id": formatted_page_id}
207
+ return True
215
208
 
216
209
  except Exception as e:
217
- self.logger.error("Error in delete_page: %s", str(e))
218
- return {"success": False, "message": f"Error: {str(e)}"}
210
+ self.logger.error("Error in archive_page: %s", str(e))
211
+ return False
219
212
 
220
213
  async def get_last_edited_time(self) -> Optional[str]:
221
214
  """
222
215
  Retrieve the last edited time of the database.
223
216
 
224
217
  Returns:
225
- ISO 8601 timestamp string of the last database edit, or None if request fails
218
+ ISO 8601 timestamp string of the last database edit, or None if request fails.
226
219
  """
227
220
  try:
228
- response = await self._client.get(f"databases/{self.database_id}")
229
-
230
- if response and "last_edited_time" in response:
231
- return response["last_edited_time"]
221
+ db = await self._client.get_database(self.database_id)
232
222
 
233
- self.logger.warning(
234
- "Could not retrieve last_edited_time for database %s", self.database_id
235
- )
236
- return None
223
+ return db.last_edited_time
237
224
 
238
225
  except Exception as e:
239
- self.logger.error("Error fetching last_edited_time: %s", str(e))
226
+ self.logger.error(
227
+ "Error fetching last_edited_time for database %s: %s",
228
+ self.database_id,
229
+ str(e),
230
+ )
240
231
  return None
241
232
 
242
233
  async def close(self) -> None:
@@ -21,11 +21,6 @@ class NotionDatabaseFactory(LoggingMixin):
21
21
  Provides methods for creating managers by database ID or name.
22
22
  """
23
23
 
24
- @classmethod
25
- def class_logger(cls):
26
- """Class logger - for class methods"""
27
- return logging.getLogger(cls.__name__)
28
-
29
24
  @classmethod
30
25
  async def from_database_id(
31
26
  cls, database_id: str, token: Optional[str] = None
@@ -40,14 +35,12 @@ class NotionDatabaseFactory(LoggingMixin):
40
35
  Returns:
41
36
  An initialized NotionDatabaseManager instance
42
37
  """
43
- logger = cls.class_logger()
44
-
45
38
  try:
46
39
  formatted_id = format_uuid(database_id) or database_id
47
40
 
48
41
  manager = NotionDatabase(formatted_id, token)
49
42
 
50
- logger.info(
43
+ cls.logger.info(
51
44
  "Successfully created database manager for ID: %s", formatted_id
52
45
  )
53
46
  return manager
@@ -58,7 +51,7 @@ class NotionDatabaseFactory(LoggingMixin):
58
51
  raise
59
52
  except Exception as e:
60
53
  error_msg = f"Error connecting to database {database_id}: {str(e)}"
61
- logger.error(error_msg)
54
+ cls.logger.error(error_msg)
62
55
  raise DatabaseConnectionError(error_msg) from e
63
56
 
64
57
  @classmethod
@@ -76,13 +69,12 @@ class NotionDatabaseFactory(LoggingMixin):
76
69
  Returns:
77
70
  An initialized NotionDatabaseManager instance
78
71
  """
79
- logger = cls.class_logger()
80
- logger.debug("Searching for database with name: %s", database_name)
72
+ cls.logger.debug("Searching for database with name: %s", database_name)
81
73
 
82
74
  client = NotionClient(token=token)
83
75
 
84
76
  try:
85
- logger.debug("Using search endpoint to find databases")
77
+ cls.logger.debug("Using search endpoint to find databases")
86
78
 
87
79
  search_payload = {
88
80
  "filter": {"property": "object", "value": "database"},
@@ -93,17 +85,19 @@ class NotionDatabaseFactory(LoggingMixin):
93
85
 
94
86
  if not response or "results" not in response:
95
87
  error_msg = "Failed to fetch databases using search endpoint"
96
- logger.error(error_msg)
88
+ cls.logger.error(error_msg)
97
89
  raise DatabaseConnectionError(error_msg)
98
90
 
99
91
  databases = response.get("results", [])
100
92
 
101
93
  if not databases:
102
94
  error_msg = "No databases found"
103
- logger.warning(error_msg)
95
+ cls.logger.warning(error_msg)
104
96
  raise DatabaseNotFoundException(database_name, error_msg)
105
97
 
106
- logger.debug("Found %d databases, searching for best match", len(databases))
98
+ cls.logger.debug(
99
+ "Found %d databases, searching for best match", len(databases)
100
+ )
107
101
 
108
102
  best_match = None
109
103
  best_score = 0
@@ -121,19 +115,19 @@ class NotionDatabaseFactory(LoggingMixin):
121
115
 
122
116
  if best_score < 0.6 or not best_match:
123
117
  error_msg = f"No good database name match found for '{database_name}'. Best match had score {best_score:.2f}"
124
- logger.warning(error_msg)
118
+ cls.logger.warning(error_msg)
125
119
  raise DatabaseNotFoundException(database_name, error_msg)
126
120
 
127
121
  database_id = best_match.get("id")
128
122
 
129
123
  if not database_id:
130
124
  error_msg = "Best match database has no ID"
131
- logger.error(error_msg)
125
+ cls.logger.error(error_msg)
132
126
  raise DatabaseParsingError(error_msg)
133
127
 
134
128
  matched_name = cls._extract_title_from_database(best_match)
135
129
 
136
- logger.info(
130
+ cls.logger.info(
137
131
  "Found matching database: '%s' (ID: %s) with score: %.2f",
138
132
  matched_name,
139
133
  database_id,
@@ -142,7 +136,9 @@ class NotionDatabaseFactory(LoggingMixin):
142
136
 
143
137
  manager = NotionDatabase(database_id, token)
144
138
 
145
- logger.info("Successfully created database manager for '%s'", matched_name)
139
+ cls.logger.info(
140
+ "Successfully created database manager for '%s'", matched_name
141
+ )
146
142
  await client.close()
147
143
  return manager
148
144
 
@@ -151,7 +147,7 @@ class NotionDatabaseFactory(LoggingMixin):
151
147
  raise
152
148
  except Exception as e:
153
149
  error_msg = f"Error finding database by name: {str(e)}"
154
- logger.error(error_msg)
150
+ cls.logger.error(error_msg)
155
151
  await client.close()
156
152
  raise DatabaseConnectionError(error_msg) from e
157
153
 
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
3
 
4
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
- from notionary.elements.prompts.element_prompt_content import (
5
+ from notionary.prompting.element_prompt_content import (
6
6
  ElementPromptBuilder,
7
7
  ElementPromptContent,
8
8
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -65,12 +65,6 @@ class BulletedListElement(NotionBlockElement):
65
65
  "Use for lists where order doesn't matter, such as features, options, or items without hierarchy."
66
66
  )
67
67
  .with_syntax("- Item text")
68
- .with_examples(
69
- [
70
- "- First item\n- Second item\n- Third item",
71
- "* Apple\n* Banana\n* Cherry",
72
- "+ Task A\n+ Task B",
73
- ]
74
- )
68
+ .with_standard_markdown()
75
69
  .build()
76
70
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional
3
3
 
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional
3
3
 
4
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
- from notionary.elements.prompts.element_prompt_content import (
5
+ from notionary.prompting.element_prompt_content import (
6
6
  ElementPromptBuilder,
7
7
  ElementPromptContent,
8
8
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional
3
3
 
4
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
- from notionary.elements.prompts.element_prompt_content import (
5
+ from notionary.prompting.element_prompt_content import (
6
6
  ElementPromptBuilder,
7
7
  ElementPromptContent,
8
8
  )
@@ -86,12 +86,6 @@ class HeadingElement(NotionBlockElement):
86
86
  "Use to group content into sections and define a visual hierarchy."
87
87
  )
88
88
  .with_syntax("## Your Heading Text")
89
- .with_examples(
90
- [
91
- "# Main Title",
92
- "## Section Title",
93
- "### Subsection Title",
94
- ]
95
- )
89
+ .with_standard_markdown()
96
90
  .build()
97
91
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -3,7 +3,7 @@ from typing import Dict, Any, Optional, List
3
3
  from typing_extensions import override
4
4
 
5
5
  from notionary.elements.notion_block_element import NotionBlockElement
6
- from notionary.elements.prompts.element_prompt_content import (
6
+ from notionary.prompting.element_prompt_content import (
7
7
  ElementPromptBuilder,
8
8
  ElementPromptContent,
9
9
  )
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Any, Optional
2
2
  from abc import ABC
3
3
 
4
- from notionary.elements.prompts.element_prompt_content import ElementPromptContent
4
+ from notionary.prompting.element_prompt_content import ElementPromptContent
5
5
 
6
6
 
7
7
  class NotionBlockElement(ABC):
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -67,11 +67,6 @@ class NumberedListElement(NotionBlockElement):
67
67
  "Use for lists where order matters, such as steps, rankings, or sequential items."
68
68
  )
69
69
  .with_syntax("1. Item text")
70
- .with_examples(
71
- [
72
- "1. First step\n2. Second step\n3. Third step",
73
- "1. Gather materials\n2. Assemble parts\n3. Test the result",
74
- ]
75
- )
70
+ .with_standard_markdown()
76
71
  .build()
77
72
  )
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Any, Optional
2
2
 
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.element_prompt_content import (
4
+ from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
@@ -1,50 +1,94 @@
1
- from typing import Dict, Any, Optional, List, Type
1
+ from __future__ import annotations
2
+ from typing import Dict, Any, Optional, List, Set, Type
2
3
 
3
4
  from notionary.elements.notion_block_element import NotionBlockElement
4
- from notionary.elements.prompts.synthax_prompt_builder import (
5
- MarkdownSyntaxPromptBuilder,
5
+ from notionary.prompting.markdown_syntax_prompt_generator import (
6
+ MarkdownSyntaxPromptGenerator,
6
7
  )
7
8
  from notionary.elements.text_inline_formatter import TextInlineFormatter
8
9
 
10
+ from notionary.elements.notion_block_element import NotionBlockElement
11
+
9
12
 
10
- class BlockElementRegistry:
13
+ class BlockRegistry:
11
14
  """Registry of elements that can convert between Markdown and Notion."""
12
15
 
13
16
  def __init__(self, elements=None):
14
17
  """
15
18
  Initialize a new registry instance.
19
+
20
+ Args:
21
+ elements: Initial elements to register
22
+ builder: The builder that created this registry (optional)
16
23
  """
17
24
  self._elements: List[NotionBlockElement] = []
25
+ self._element_types: Set[Type[NotionBlockElement]] = set()
18
26
 
19
27
  if elements:
20
28
  for element in elements:
21
29
  self.register(element)
22
30
 
23
- def register(self, element_class: Type[NotionBlockElement]):
24
- """Register an element class."""
31
+ def to_builder(self):
32
+ """
33
+ Convert this registry to a builder for modifications.
34
+ Imports only when needed to avoid circular imports.
35
+ """
36
+ from notionary.elements.registry.block_registry_builder import (
37
+ BlockRegistryBuilder,
38
+ )
39
+
40
+ builder = BlockRegistryBuilder()
41
+ for element in self._elements:
42
+ builder.add_element(element)
43
+ return builder
44
+
45
+ @property
46
+ def builder(self):
47
+ """
48
+ Returns a new builder pre-configured with the current registry elements.
49
+ Uses lazy import to avoid circular dependencies.
50
+ """
51
+ return self.to_builder()
52
+
53
+ def register(self, element_class: Type[NotionBlockElement]) -> bool:
54
+ """
55
+ Register an element class.
56
+
57
+ Args:
58
+ element_class: The element class to register
59
+
60
+ Returns:
61
+ bool: True if element was added, False if it already existed
62
+ """
63
+ if element_class in self._element_types:
64
+ return False
65
+
25
66
  self._elements.append(element_class)
26
- return self
67
+ self._element_types.add(element_class)
68
+ return True
27
69
 
28
70
  def deregister(self, element_class: Type[NotionBlockElement]) -> bool:
29
71
  """
30
72
  Deregister an element class.
31
73
  """
32
- if element_class in self._elements:
74
+ if element_class in self._element_types:
33
75
  self._elements.remove(element_class)
76
+ self._element_types.remove(element_class)
34
77
  return True
35
78
  return False
36
79
 
37
80
  def contains(self, element_class: Type[NotionBlockElement]) -> bool:
38
81
  """
39
- Check if the registry contains the specified element class.
82
+ Prüft, ob ein bestimmtes Element im Registry enthalten ist.
83
+
84
+ Args:
85
+ element_class: Die zu prüfende Element-Klasse
86
+
87
+ Returns:
88
+ bool: True, wenn das Element enthalten ist, sonst False
40
89
  """
41
90
  return element_class in self._elements
42
91
 
43
- def clear(self):
44
- """Clear the registry completely."""
45
- self._elements.clear()
46
- return self
47
-
48
92
  def find_markdown_handler(self, text: str) -> Optional[Type[NotionBlockElement]]:
49
93
  """Find an element that can handle the given markdown text."""
50
94
  for element in self._elements:
@@ -52,15 +96,6 @@ class BlockElementRegistry:
52
96
  return element
53
97
  return None
54
98
 
55
- def find_notion_handler(
56
- self, block: Dict[str, Any]
57
- ) -> Optional[Type[NotionBlockElement]]:
58
- """Find an element that can handle the given Notion block."""
59
- for element in self._elements:
60
- if element.match_notion(block):
61
- return element
62
- return None
63
-
64
99
  def markdown_to_notion(self, text: str) -> Optional[Dict[str, Any]]:
65
100
  """Convert markdown to Notion block using registered elements."""
66
101
  handler = self.find_markdown_handler(text)
@@ -70,7 +105,7 @@ class BlockElementRegistry:
70
105
 
71
106
  def notion_to_markdown(self, block: Dict[str, Any]) -> Optional[str]:
72
107
  """Convert Notion block to markdown using registered elements."""
73
- handler = self.find_notion_handler(block)
108
+ handler = self._find_notion_handler(block)
74
109
  if handler:
75
110
  return handler.notion_to_markdown(block)
76
111
  return None
@@ -83,7 +118,7 @@ class BlockElementRegistry:
83
118
  """Get all registered elements."""
84
119
  return self._elements.copy()
85
120
 
86
- def generate_llm_prompt(self) -> str:
121
+ def get_notion_markdown_syntax_prompt(self) -> str:
87
122
  """
88
123
  Generates an LLM system prompt that describes the Markdown syntax of all registered elements.
89
124
  """
@@ -93,4 +128,13 @@ class BlockElementRegistry:
93
128
  if "TextInlineFormatter" not in formatter_names:
94
129
  element_classes = [TextInlineFormatter] + element_classes
95
130
 
96
- return MarkdownSyntaxPromptBuilder.generate_system_prompt(element_classes)
131
+ return MarkdownSyntaxPromptGenerator.generate_system_prompt(element_classes)
132
+
133
+ def _find_notion_handler(
134
+ self, block: Dict[str, Any]
135
+ ) -> Optional[Type[NotionBlockElement]]:
136
+ """Find an element that can handle the given Notion block."""
137
+ for element in self._elements:
138
+ if element.match_notion(block):
139
+ return element
140
+ return None