notionary 0.1.2__py3-none-any.whl → 0.1.3__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/core/__init__.py +0 -0
- notionary/core/converters/__init__.py +50 -0
- notionary/core/converters/elements/__init__.py +0 -0
- notionary/core/converters/elements/bookmark_element.py +224 -0
- notionary/core/converters/elements/callout_element.py +179 -0
- notionary/core/converters/elements/code_block_element.py +153 -0
- notionary/core/converters/elements/column_element.py +294 -0
- notionary/core/converters/elements/divider_element.py +73 -0
- notionary/core/converters/elements/heading_element.py +84 -0
- notionary/core/converters/elements/image_element.py +130 -0
- notionary/core/converters/elements/list_element.py +130 -0
- notionary/core/converters/elements/notion_block_element.py +51 -0
- notionary/core/converters/elements/paragraph_element.py +73 -0
- notionary/core/converters/elements/qoute_element.py +242 -0
- notionary/core/converters/elements/table_element.py +306 -0
- notionary/core/converters/elements/text_inline_formatter.py +294 -0
- notionary/core/converters/elements/todo_lists.py +114 -0
- notionary/core/converters/elements/toggle_element.py +205 -0
- notionary/core/converters/elements/video_element.py +159 -0
- notionary/core/converters/markdown_to_notion_converter.py +482 -0
- notionary/core/converters/notion_to_markdown_converter.py +45 -0
- notionary/core/converters/registry/__init__.py +0 -0
- notionary/core/converters/registry/block_element_registry.py +234 -0
- notionary/core/converters/registry/block_element_registry_builder.py +280 -0
- notionary/core/database/database_info_service.py +43 -0
- notionary/core/database/database_query_service.py +73 -0
- notionary/core/database/database_schema_service.py +57 -0
- notionary/core/database/models/page_result.py +10 -0
- notionary/core/database/notion_database_manager.py +332 -0
- notionary/core/database/notion_database_manager_factory.py +233 -0
- notionary/core/database/notion_database_schema.py +415 -0
- notionary/core/database/notion_database_writer.py +390 -0
- notionary/core/database/page_service.py +161 -0
- notionary/core/notion_client.py +134 -0
- notionary/core/page/meta_data/metadata_editor.py +37 -0
- notionary/core/page/notion_page_manager.py +110 -0
- notionary/core/page/page_content_manager.py +85 -0
- notionary/core/page/property_formatter.py +97 -0
- notionary/exceptions/database_exceptions.py +76 -0
- notionary/exceptions/page_creation_exception.py +9 -0
- notionary/util/logging_mixin.py +47 -0
- notionary/util/singleton_decorator.py +20 -0
- notionary/util/uuid_utils.py +24 -0
- {notionary-0.1.2.dist-info → notionary-0.1.3.dist-info}/METADATA +1 -1
- notionary-0.1.3.dist-info/RECORD +49 -0
- notionary-0.1.2.dist-info/RECORD +0 -6
- {notionary-0.1.2.dist-info → notionary-0.1.3.dist-info}/WHEEL +0 -0
- {notionary-0.1.2.dist-info → notionary-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.1.2.dist-info → notionary-0.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
from typing import Dict, Any, Optional, List, Type
|
2
|
+
|
3
|
+
from notionary.core.converters.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
|
5
|
+
|
6
|
+
|
7
|
+
class BlockElementRegistry:
|
8
|
+
"""Registry of elements that can convert between Markdown and Notion."""
|
9
|
+
|
10
|
+
def __init__(self, elements=None):
|
11
|
+
"""
|
12
|
+
Initialize a new registry instance.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
elements: Optional list of NotionBlockElement classes to register at creation
|
16
|
+
"""
|
17
|
+
self._elements = []
|
18
|
+
|
19
|
+
# Register initial elements if provided
|
20
|
+
if elements:
|
21
|
+
for element in elements:
|
22
|
+
self.register(element)
|
23
|
+
|
24
|
+
def register(self, element_class: Type[NotionBlockElement]):
|
25
|
+
"""Register an element class."""
|
26
|
+
self._elements.append(element_class)
|
27
|
+
return self
|
28
|
+
|
29
|
+
def deregister(self, element_class: Type[NotionBlockElement]) -> bool:
|
30
|
+
"""
|
31
|
+
Deregister an element class.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
element_class: The element class to remove from the registry
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
bool: True if the element was removed, False if it wasn't in the registry
|
38
|
+
"""
|
39
|
+
if element_class in self._elements:
|
40
|
+
self._elements.remove(element_class)
|
41
|
+
return True
|
42
|
+
return False
|
43
|
+
|
44
|
+
def clear(self):
|
45
|
+
"""Clear the registry completely."""
|
46
|
+
self._elements.clear()
|
47
|
+
return self
|
48
|
+
|
49
|
+
def find_markdown_handler(self, text: str) -> Optional[Type[NotionBlockElement]]:
|
50
|
+
"""Find an element that can handle the given markdown text."""
|
51
|
+
for element in self._elements:
|
52
|
+
if element.match_markdown(text):
|
53
|
+
return element
|
54
|
+
return None
|
55
|
+
|
56
|
+
def find_notion_handler(
|
57
|
+
self, block: Dict[str, Any]
|
58
|
+
) -> Optional[Type[NotionBlockElement]]:
|
59
|
+
"""Find an element that can handle the given Notion block."""
|
60
|
+
for element in self._elements:
|
61
|
+
if element.match_notion(block):
|
62
|
+
return element
|
63
|
+
return None
|
64
|
+
|
65
|
+
def markdown_to_notion(self, text: str) -> Optional[Dict[str, Any]]:
|
66
|
+
"""Convert markdown to Notion block using registered elements."""
|
67
|
+
handler = self.find_markdown_handler(text)
|
68
|
+
if handler:
|
69
|
+
return handler.markdown_to_notion(text)
|
70
|
+
return None
|
71
|
+
|
72
|
+
def notion_to_markdown(self, block: Dict[str, Any]) -> Optional[str]:
|
73
|
+
"""Convert Notion block to markdown using registered elements."""
|
74
|
+
handler = self.find_notion_handler(block)
|
75
|
+
if handler:
|
76
|
+
return handler.notion_to_markdown(block)
|
77
|
+
return None
|
78
|
+
|
79
|
+
def get_multiline_elements(self) -> List[Type[NotionBlockElement]]:
|
80
|
+
"""Get all registered multiline elements."""
|
81
|
+
return [element for element in self._elements if element.is_multiline()]
|
82
|
+
|
83
|
+
def get_elements(self) -> List[Type[NotionBlockElement]]:
|
84
|
+
"""Get all registered elements."""
|
85
|
+
return self._elements.copy()
|
86
|
+
|
87
|
+
def generate_llm_prompt(self) -> str:
|
88
|
+
"""
|
89
|
+
Generates an LLM system prompt that describes the Markdown syntax of all registered elements.
|
90
|
+
|
91
|
+
TextInlineFormatter is automatically added if not already registered.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
A complete system prompt for an LLM that should understand Notion-Markdown syntax
|
95
|
+
"""
|
96
|
+
# Create a copy of registered elements
|
97
|
+
element_classes = self._elements.copy()
|
98
|
+
print("Elements in registry:", element_classes)
|
99
|
+
|
100
|
+
formatter_names = [e.__name__ for e in element_classes]
|
101
|
+
if "TextInlineFormatter" not in formatter_names:
|
102
|
+
element_classes = [TextInlineFormatter] + element_classes
|
103
|
+
|
104
|
+
return MarkdownSyntaxPromptBuilder.generate_system_prompt(element_classes)
|
105
|
+
|
106
|
+
|
107
|
+
class MarkdownSyntaxPromptBuilder:
|
108
|
+
"""
|
109
|
+
Generator for LLM system prompts that describe Notion-Markdown syntax.
|
110
|
+
|
111
|
+
This class extracts information about supported Markdown patterns
|
112
|
+
and formats them optimally for LLMs.
|
113
|
+
"""
|
114
|
+
|
115
|
+
# Standard system prompt template
|
116
|
+
SYSTEM_PROMPT_TEMPLATE = """You are a knowledgeable assistant that helps users create content for Notion pages.
|
117
|
+
Notion supports standard Markdown with some special extensions for creating rich content.
|
118
|
+
|
119
|
+
{element_docs}
|
120
|
+
|
121
|
+
Important usage guidelines:
|
122
|
+
|
123
|
+
1. The backtick code fence syntax (```) should ONLY be used when creating actual code blocks or diagrams.
|
124
|
+
Do not wrap examples or regular content in backticks unless you're showing code.
|
125
|
+
|
126
|
+
2. Use inline formatting (bold, italic, highlights, etc.) across all content to enhance readability.
|
127
|
+
The highlight syntax (==text== and ==color:text==) is especially useful for emphasizing important points.
|
128
|
+
|
129
|
+
3. Notion's extensions to Markdown (like callouts, bookmarks, toggles) provide richer formatting options
|
130
|
+
than standard Markdown while maintaining the familiar Markdown syntax for basic elements.
|
131
|
+
|
132
|
+
4. You can use these Markdown extensions alongside standard Markdown to create visually appealing
|
133
|
+
and well-structured content.
|
134
|
+
|
135
|
+
5. Remember that features like highlighting with ==yellow:important== work in all text blocks including
|
136
|
+
paragraphs, lists, quotes, etc.
|
137
|
+
"""
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def generate_element_doc(element_class: Type[NotionBlockElement]) -> str:
|
141
|
+
"""
|
142
|
+
Generates documentation for a specific NotionBlockElement.
|
143
|
+
|
144
|
+
Uses the element's get_llm_prompt_content method if available.
|
145
|
+
"""
|
146
|
+
class_name = element_class.__name__
|
147
|
+
element_name = class_name.replace("Element", "")
|
148
|
+
|
149
|
+
# Start with element name as header
|
150
|
+
result = [f"## {element_name}"]
|
151
|
+
|
152
|
+
# Use get_llm_prompt_content if available
|
153
|
+
if hasattr(element_class, "get_llm_prompt_content") and callable(
|
154
|
+
getattr(element_class, "get_llm_prompt_content")
|
155
|
+
):
|
156
|
+
content = element_class.get_llm_prompt_content()
|
157
|
+
|
158
|
+
if content.get("description"):
|
159
|
+
result.append(content["description"])
|
160
|
+
|
161
|
+
if content.get("syntax"):
|
162
|
+
result.append("\n### Syntax:")
|
163
|
+
for syntax_item in content["syntax"]:
|
164
|
+
result.append(f"{syntax_item}")
|
165
|
+
|
166
|
+
if content.get("examples"):
|
167
|
+
result.append("\n### Examples:")
|
168
|
+
for example in content["examples"]:
|
169
|
+
result.append(example)
|
170
|
+
|
171
|
+
# Add any additional custom sections
|
172
|
+
for key, value in content.items():
|
173
|
+
if key not in ["description", "syntax", "examples"] and isinstance(
|
174
|
+
value, str
|
175
|
+
):
|
176
|
+
result.append(f"\n### {key.replace('_', ' ').title()}:")
|
177
|
+
result.append(value)
|
178
|
+
|
179
|
+
return "\n".join(result)
|
180
|
+
|
181
|
+
@classmethod
|
182
|
+
def generate_element_docs(
|
183
|
+
cls,
|
184
|
+
element_classes: List[Type[NotionBlockElement]],
|
185
|
+
) -> str:
|
186
|
+
"""
|
187
|
+
Generates complete documentation for all provided element classes.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
element_classes: List of NotionBlockElement classes
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Documentation text for all elements
|
194
|
+
"""
|
195
|
+
docs = [
|
196
|
+
"# Custom Markdown Syntax for Notion Blocks",
|
197
|
+
"The following custom Markdown patterns are supported for creating Notion blocks:",
|
198
|
+
]
|
199
|
+
|
200
|
+
text_formatter = None
|
201
|
+
other_elements = []
|
202
|
+
|
203
|
+
for element in element_classes:
|
204
|
+
if element.__name__ == "TextInlineFormatter":
|
205
|
+
text_formatter = element
|
206
|
+
else:
|
207
|
+
other_elements.append(element)
|
208
|
+
|
209
|
+
if text_formatter:
|
210
|
+
docs.append("\n" + cls.generate_element_doc(text_formatter))
|
211
|
+
|
212
|
+
for element in other_elements:
|
213
|
+
if element.__name__ != "InlineFormattingElement":
|
214
|
+
docs.append("\n" + cls.generate_element_doc(element))
|
215
|
+
|
216
|
+
return "\n".join(docs)
|
217
|
+
|
218
|
+
@classmethod
|
219
|
+
def generate_system_prompt(
|
220
|
+
cls,
|
221
|
+
element_classes: List[Type[NotionBlockElement]],
|
222
|
+
) -> str:
|
223
|
+
"""
|
224
|
+
Generates a complete system prompt for LLMs.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
element_classes: List of element classes to document
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Complete system prompt for an LLM
|
231
|
+
"""
|
232
|
+
element_docs = cls.generate_element_docs(element_classes)
|
233
|
+
|
234
|
+
return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)
|
@@ -0,0 +1,280 @@
|
|
1
|
+
from typing import List, Type
|
2
|
+
from collections import OrderedDict
|
3
|
+
|
4
|
+
from notionary.core.converters.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.core.converters.registry.block_element_registry import (
|
6
|
+
BlockElementRegistry,
|
7
|
+
)
|
8
|
+
|
9
|
+
from notionary.core.converters.elements.paragraph_element import ParagraphElement
|
10
|
+
from notionary.core.converters.elements.heading_element import HeadingElement
|
11
|
+
from notionary.core.converters.elements.callout_element import CalloutElement
|
12
|
+
from notionary.core.converters.elements.code_block_element import CodeBlockElement
|
13
|
+
from notionary.core.converters.elements.divider_element import DividerElement
|
14
|
+
from notionary.core.converters.elements.table_element import TableElement
|
15
|
+
from notionary.core.converters.elements.todo_lists import TodoElement
|
16
|
+
from notionary.core.converters.elements.list_element import (
|
17
|
+
BulletedListElement,
|
18
|
+
NumberedListElement,
|
19
|
+
)
|
20
|
+
from notionary.core.converters.elements.qoute_element import QuoteElement
|
21
|
+
from notionary.core.converters.elements.image_element import ImageElement
|
22
|
+
from notionary.core.converters.elements.video_element import VideoElement
|
23
|
+
from notionary.core.converters.elements.toggle_element import ToggleElement
|
24
|
+
from notionary.core.converters.elements.bookmark_element import BookmarkElement
|
25
|
+
from notionary.core.converters.elements.column_element import ColumnElement
|
26
|
+
|
27
|
+
|
28
|
+
class BlockElementRegistryBuilder:
|
29
|
+
"""
|
30
|
+
True builder for constructing BlockElementRegistry instances.
|
31
|
+
|
32
|
+
This builder allows for incremental construction of registry instances
|
33
|
+
with specific configurations of block elements.
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize a new builder with an empty element list."""
|
38
|
+
# Use OrderedDict to maintain insertion order while ensuring uniqueness
|
39
|
+
self._elements = OrderedDict()
|
40
|
+
|
41
|
+
# Profile methods - create a base configuration
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def start_empty(cls) -> "BlockElementRegistryBuilder":
|
45
|
+
"""
|
46
|
+
Start with a completely empty registry builder.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
A new builder instance with no elements
|
50
|
+
"""
|
51
|
+
return cls()
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def start_minimal(cls) -> "BlockElementRegistryBuilder":
|
55
|
+
"""
|
56
|
+
Start with a minimal set of essential elements.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
A new builder instance with basic elements
|
60
|
+
"""
|
61
|
+
builder = cls()
|
62
|
+
return (
|
63
|
+
builder.add_element(HeadingElement)
|
64
|
+
.add_element(BulletedListElement)
|
65
|
+
.add_element(NumberedListElement)
|
66
|
+
.add_element(ParagraphElement)
|
67
|
+
) # Add paragraph last as fallback
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def start_standard(cls) -> "BlockElementRegistryBuilder":
|
71
|
+
"""
|
72
|
+
Start with all standard elements in recommended order.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
A new builder instance with all standard elements
|
76
|
+
"""
|
77
|
+
builder = cls()
|
78
|
+
return (
|
79
|
+
builder.add_element(HeadingElement)
|
80
|
+
.add_element(CalloutElement)
|
81
|
+
.add_element(CodeBlockElement)
|
82
|
+
.add_element(DividerElement)
|
83
|
+
.add_element(TableElement)
|
84
|
+
.add_element(ColumnElement)
|
85
|
+
.add_element(BulletedListElement)
|
86
|
+
.add_element(NumberedListElement)
|
87
|
+
.add_element(ToggleElement)
|
88
|
+
.add_element(QuoteElement)
|
89
|
+
.add_element(TodoElement)
|
90
|
+
.add_element(BookmarkElement)
|
91
|
+
.add_element(ImageElement)
|
92
|
+
.add_element(VideoElement)
|
93
|
+
.add_element(ParagraphElement)
|
94
|
+
) # Add paragraph last as fallback
|
95
|
+
|
96
|
+
# Element manipulation methods
|
97
|
+
|
98
|
+
def add_element(
|
99
|
+
self, element_class: Type[NotionBlockElement]
|
100
|
+
) -> "BlockElementRegistryBuilder":
|
101
|
+
"""
|
102
|
+
Add an element class to the registry configuration.
|
103
|
+
If the element already exists, it's moved to the end.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
element_class: The element class to add
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
Self for method chaining
|
110
|
+
"""
|
111
|
+
# Remove if exists (to update the order) and add to the end
|
112
|
+
self._elements.pop(element_class.__name__, None)
|
113
|
+
self._elements[element_class.__name__] = element_class
|
114
|
+
return self
|
115
|
+
|
116
|
+
def add_elements(
|
117
|
+
self, element_classes: List[Type[NotionBlockElement]]
|
118
|
+
) -> "BlockElementRegistryBuilder":
|
119
|
+
"""
|
120
|
+
Add multiple element classes to the registry configuration.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
element_classes: List of element classes to add
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
Self for method chaining
|
127
|
+
"""
|
128
|
+
for element_class in element_classes:
|
129
|
+
self.add_element(element_class)
|
130
|
+
return self
|
131
|
+
|
132
|
+
def remove_element(
|
133
|
+
self, element_class: Type[NotionBlockElement]
|
134
|
+
) -> "BlockElementRegistryBuilder":
|
135
|
+
"""
|
136
|
+
Remove an element class from the registry configuration.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
element_class: The element class to remove
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Self for method chaining
|
143
|
+
"""
|
144
|
+
self._elements.pop(element_class.__name__, None)
|
145
|
+
return self
|
146
|
+
|
147
|
+
def move_element_to_end(
|
148
|
+
self, element_class: Type[NotionBlockElement]
|
149
|
+
) -> "BlockElementRegistryBuilder":
|
150
|
+
"""
|
151
|
+
Move an existing element to the end of the registry.
|
152
|
+
If the element doesn't exist, it will be added.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
element_class: The element class to move
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
Self for method chaining
|
159
|
+
"""
|
160
|
+
return self.add_element(element_class) # add_element already handles this logic
|
161
|
+
|
162
|
+
def ensure_paragraph_at_end(self) -> "BlockElementRegistryBuilder":
|
163
|
+
"""
|
164
|
+
Ensure ParagraphElement is the last element in the registry.
|
165
|
+
If it doesn't exist, it will be added.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Self for method chaining
|
169
|
+
"""
|
170
|
+
return self.move_element_to_end(ParagraphElement)
|
171
|
+
|
172
|
+
# Specialized configuration methods
|
173
|
+
|
174
|
+
def with_list_support(self) -> "BlockElementRegistryBuilder":
|
175
|
+
"""
|
176
|
+
Add support for list elements.
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
Self for method chaining
|
180
|
+
"""
|
181
|
+
return self.add_element(BulletedListElement).add_element(NumberedListElement)
|
182
|
+
|
183
|
+
def with_code_support(self) -> "BlockElementRegistryBuilder":
|
184
|
+
"""
|
185
|
+
Add support for code blocks.
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
Self for method chaining
|
189
|
+
"""
|
190
|
+
return self.add_element(CodeBlockElement)
|
191
|
+
|
192
|
+
def with_table_support(self) -> "BlockElementRegistryBuilder":
|
193
|
+
"""
|
194
|
+
Add support for tables.
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
Self for method chaining
|
198
|
+
"""
|
199
|
+
return self.add_element(TableElement)
|
200
|
+
|
201
|
+
def with_rich_content(self) -> "BlockElementRegistryBuilder":
|
202
|
+
"""
|
203
|
+
Add support for rich content elements (callouts, toggles, etc.).
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Self for method chaining
|
207
|
+
"""
|
208
|
+
return (
|
209
|
+
self.add_element(CalloutElement)
|
210
|
+
.add_element(ToggleElement)
|
211
|
+
.add_element(QuoteElement)
|
212
|
+
)
|
213
|
+
|
214
|
+
def with_media_support(self) -> "BlockElementRegistryBuilder":
|
215
|
+
"""
|
216
|
+
Add support for media elements (images, videos).
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
Self for method chaining
|
220
|
+
"""
|
221
|
+
return self.add_element(ImageElement).add_element(VideoElement)
|
222
|
+
|
223
|
+
def with_task_support(self) -> "BlockElementRegistryBuilder":
|
224
|
+
"""
|
225
|
+
Add support for task-related elements (todos).
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
Self for method chaining
|
229
|
+
"""
|
230
|
+
return self.add_element(TodoElement)
|
231
|
+
|
232
|
+
def build(self) -> BlockElementRegistry:
|
233
|
+
"""
|
234
|
+
Build and return the configured BlockElementRegistry instance.
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
A configured BlockElementRegistry instance
|
238
|
+
"""
|
239
|
+
registry = BlockElementRegistry()
|
240
|
+
|
241
|
+
# Add elements in the recorded order
|
242
|
+
for element_class in self._elements.values():
|
243
|
+
registry.register(element_class)
|
244
|
+
|
245
|
+
return registry
|
246
|
+
|
247
|
+
@classmethod
|
248
|
+
def create_standard_registry(cls) -> BlockElementRegistry:
|
249
|
+
"""
|
250
|
+
Factory method to directly create a standard registry.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
A fully configured registry instance
|
254
|
+
"""
|
255
|
+
return cls.start_standard().build()
|
256
|
+
|
257
|
+
@classmethod
|
258
|
+
def create_minimal_registry(cls) -> BlockElementRegistry:
|
259
|
+
"""
|
260
|
+
Factory method to directly create a minimal registry.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
A minimal registry instance
|
264
|
+
"""
|
265
|
+
return cls.start_minimal().build()
|
266
|
+
|
267
|
+
@classmethod
|
268
|
+
def create_custom_registry(
|
269
|
+
cls, element_classes: List[Type[NotionBlockElement]]
|
270
|
+
) -> BlockElementRegistry:
|
271
|
+
"""
|
272
|
+
Factory method to directly create a custom registry.
|
273
|
+
|
274
|
+
Args:
|
275
|
+
element_classes: List of element classes to register
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
A custom configured registry instance
|
279
|
+
"""
|
280
|
+
return cls().add_elements(element_classes).build()
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from notionary.core.notion_client import NotionClient
|
3
|
+
|
4
|
+
|
5
|
+
class DatabaseInfoService:
|
6
|
+
"""Service für den Zugriff auf Datenbankinformationen"""
|
7
|
+
|
8
|
+
def __init__(self, client: NotionClient, database_id: str):
|
9
|
+
self._client = client
|
10
|
+
self.database_id = database_id
|
11
|
+
self._title = None
|
12
|
+
|
13
|
+
async def fetch_database_title(self) -> str:
|
14
|
+
"""
|
15
|
+
Fetch the database title from the Notion API.
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
The database title or "Untitled" if no title is found
|
19
|
+
"""
|
20
|
+
db_details = await self._client.get(f"databases/{self.database_id}")
|
21
|
+
if not db_details:
|
22
|
+
return "Untitled"
|
23
|
+
|
24
|
+
title = "Untitled"
|
25
|
+
if "title" in db_details:
|
26
|
+
title_parts = []
|
27
|
+
for text_obj in db_details["title"]:
|
28
|
+
if "plain_text" in text_obj:
|
29
|
+
title_parts.append(text_obj["plain_text"])
|
30
|
+
|
31
|
+
if title_parts:
|
32
|
+
title = "".join(title_parts)
|
33
|
+
|
34
|
+
return title
|
35
|
+
|
36
|
+
@property
|
37
|
+
def title(self) -> Optional[str]:
|
38
|
+
return self._title
|
39
|
+
|
40
|
+
async def load_title(self) -> str:
|
41
|
+
"""Lädt den Titel der Datenbank und speichert ihn im Cache"""
|
42
|
+
self._title = await self.fetch_database_title()
|
43
|
+
return self._title
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from typing import Any, AsyncGenerator, Dict, List, Optional
|
2
|
+
from notionary.core.database.notion_database_schema import NotionDatabaseSchema
|
3
|
+
from notionary.core.page.notion_page_manager import NotionPageManager
|
4
|
+
|
5
|
+
|
6
|
+
class DatabaseQueryService:
|
7
|
+
"""Service für Datenbankabfragen und Iterations"""
|
8
|
+
|
9
|
+
def __init__(self, schema: NotionDatabaseSchema):
|
10
|
+
self._schema = schema
|
11
|
+
|
12
|
+
async def get_pages(
|
13
|
+
self,
|
14
|
+
database_id: str,
|
15
|
+
limit: int = 100,
|
16
|
+
filter_conditions: Optional[Dict[str, Any]] = None,
|
17
|
+
sorts: Optional[List[Dict[str, Any]]] = None,
|
18
|
+
) -> List[NotionPageManager]:
|
19
|
+
"""
|
20
|
+
Get all pages from the database.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
database_id: The database ID to query
|
24
|
+
limit: Maximum number of pages to retrieve
|
25
|
+
filter_conditions: Optional filter to apply to the database query
|
26
|
+
sorts: Optional sort instructions for the database query
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
List of NotionPageManager instances for each page
|
30
|
+
"""
|
31
|
+
pages: List[NotionPageManager] = []
|
32
|
+
count = 0
|
33
|
+
|
34
|
+
async for page in self.iter_pages(
|
35
|
+
database_id,
|
36
|
+
page_size=min(limit, 100),
|
37
|
+
filter_conditions=filter_conditions,
|
38
|
+
sorts=sorts,
|
39
|
+
):
|
40
|
+
pages.append(page)
|
41
|
+
count += 1
|
42
|
+
|
43
|
+
if count >= limit:
|
44
|
+
break
|
45
|
+
|
46
|
+
return pages
|
47
|
+
|
48
|
+
async def iter_pages(
|
49
|
+
self,
|
50
|
+
database_id: str,
|
51
|
+
page_size: int = 100,
|
52
|
+
filter_conditions: Optional[Dict[str, Any]] = None,
|
53
|
+
sorts: Optional[List[Dict[str, Any]]] = None,
|
54
|
+
) -> AsyncGenerator[NotionPageManager, None]:
|
55
|
+
"""
|
56
|
+
Asynchronous generator that yields pages from the database.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
database_id: The database ID to query
|
60
|
+
page_size: Number of pages to fetch per request
|
61
|
+
filter_conditions: Optional filter to apply to the database query
|
62
|
+
sorts: Optional sort instructions for the database query
|
63
|
+
|
64
|
+
Yields:
|
65
|
+
NotionPageManager instances for each page
|
66
|
+
"""
|
67
|
+
async for page_manager in self._schema.iter_database_pages(
|
68
|
+
database_id=database_id,
|
69
|
+
page_size=page_size,
|
70
|
+
filter_conditions=filter_conditions,
|
71
|
+
sorts=sorts,
|
72
|
+
):
|
73
|
+
yield page_manager
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from typing import Dict, List
|
2
|
+
from notionary.core.database.notion_database_schema import NotionDatabaseSchema
|
3
|
+
|
4
|
+
|
5
|
+
class DatabaseSchemaService:
|
6
|
+
"""Service für den Zugriff auf Datenbankschema-Informationen"""
|
7
|
+
|
8
|
+
def __init__(self, schema: NotionDatabaseSchema):
|
9
|
+
self._schema = schema
|
10
|
+
|
11
|
+
async def get_property_types(self) -> Dict[str, str]:
|
12
|
+
"""
|
13
|
+
Get all property types for the database.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
Dictionary mapping property names to their types
|
17
|
+
"""
|
18
|
+
return await self._schema.get_property_types()
|
19
|
+
|
20
|
+
async def get_select_options(self, property_name: str) -> List[Dict[str, str]]:
|
21
|
+
"""
|
22
|
+
Get options for a select, multi-select, or status property.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
property_name: Name of the property
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
List of select options with name, id, and color (if available)
|
29
|
+
"""
|
30
|
+
options = await self._schema.get_select_options(property_name)
|
31
|
+
return [
|
32
|
+
{
|
33
|
+
"name": option.get("name", ""),
|
34
|
+
"id": option.get("id", ""),
|
35
|
+
"color": option.get("color", ""),
|
36
|
+
}
|
37
|
+
for option in options
|
38
|
+
]
|
39
|
+
|
40
|
+
async def get_relation_options(
|
41
|
+
self, property_name: str, limit: int = 100
|
42
|
+
) -> List[Dict[str, str]]:
|
43
|
+
"""
|
44
|
+
Get available options for a relation property.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
property_name: Name of the relation property
|
48
|
+
limit: Maximum number of options to retrieve
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
List of relation options with id and title
|
52
|
+
"""
|
53
|
+
options = await self._schema.get_relation_options(property_name, limit)
|
54
|
+
return [
|
55
|
+
{"id": option.get("id", ""), "title": option.get("title", "")}
|
56
|
+
for option in options
|
57
|
+
]
|