notionary 0.2.18__py3-none-any.whl → 0.2.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +263 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +42 -104
- notionary/blocks/audio/audio_markdown_node.py +3 -1
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +30 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +46 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +40 -55
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +40 -89
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +7 -0
- notionary/blocks/child_database/child_database_models.py +19 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +55 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +53 -187
- notionary/blocks/code/code_markdown_node.py +13 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +40 -314
- notionary/blocks/column/column_list_element.py +37 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +26 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +47 -114
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +80 -0
- notionary/blocks/equation/equation_element_markdown_node.py +36 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +93 -0
- notionary/blocks/file/file_element_markdown_node.py +35 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +67 -72
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +84 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/models.py +172 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +33 -58
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +27 -69
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +91 -0
- notionary/blocks/pdf/pdf_markdown_node.py +35 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +31 -65
- notionary/blocks/quote/quote_markdown_node.py +4 -1
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +75 -91
- notionary/blocks/registry/block_registry_builder.py +107 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/rich_text_models.py +188 -0
- notionary/blocks/rich_text/text_inline_formatter.py +125 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +48 -241
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +51 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +38 -95
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +57 -264
- notionary/blocks/toggle/toggle_markdown_node.py +24 -14
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +61 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +67 -143
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +3 -1
- notionary/database/client.py +3 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +2 -1
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +722 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +9 -10
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +99 -52
- notionary/page/notion_text_length_utils.py +119 -0
- notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
- notionary/page/reader/handler/__init__.py +17 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +43 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +60 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +69 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +22 -0
- notionary/page/writer/handler/code_handler.py +100 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +92 -0
- notionary/page/writer/handler/table_handler.py +130 -0
- notionary/page/writer/handler/toggle_handler.py +153 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
- notionary/page/writer/markdown_to_notion_converter.py +76 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +4 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +3 -2
- notionary/user/notion_user_provider.py +1 -1
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +3 -2
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
- notionary-0.2.21.dist-info/RECORD +185 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/__init__.py +0 -0
- notionary/blocks/shared/models.py +0 -710
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -52
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary-0.2.18.dist-info/RECORD +0 -149
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,182 +1,106 @@
|
|
1
|
-
import
|
2
|
-
from typing import Dict, Any, Optional, List
|
1
|
+
from __future__ import annotations
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
import re
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.file.file_element_models import ExternalFile, FileBlock, FileType
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
9
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
10
|
+
CreateParagraphBlock,
|
11
|
+
ParagraphBlock,
|
9
12
|
)
|
13
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
14
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
15
|
+
from notionary.blocks.types import BlockType
|
16
|
+
from notionary.blocks.video.video_element_models import CreateVideoBlock
|
10
17
|
|
11
18
|
|
12
|
-
class VideoElement(
|
19
|
+
class VideoElement(BaseBlockElement):
|
13
20
|
"""
|
14
21
|
Handles conversion between Markdown video embeds and Notion video blocks.
|
15
22
|
|
16
23
|
Markdown video syntax:
|
17
|
-
- [video](https://example.com/video.mp4) -
|
18
|
-
- [video](https://example.com/video.mp4 "Caption") -
|
24
|
+
- [video](https://example.com/video.mp4) - URL only
|
25
|
+
- [video](https://example.com/video.mp4 "Caption") - URL + caption
|
19
26
|
|
20
|
-
|
21
|
-
- URL is the required video URL
|
22
|
-
- Caption is an optional descriptive text (enclosed in quotes)
|
23
|
-
|
24
|
-
Supports various video URLs including YouTube, Vimeo, and direct video file links.
|
27
|
+
Supports YouTube, Vimeo, and direct file URLs.
|
25
28
|
"""
|
26
29
|
|
27
|
-
# Regex pattern for video syntax with optional caption
|
28
30
|
PATTERN = re.compile(
|
29
|
-
r"^\[video\]\(" #
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
r"^\[video\]\(" # prefix
|
32
|
+
r"(https?://[^\s\"]+)" # URL
|
33
|
+
r"(?:\s+\"([^\"]+)\")?" # optional caption
|
34
|
+
r"\)$"
|
33
35
|
)
|
34
36
|
|
35
|
-
# YouTube URL patterns
|
36
37
|
YOUTUBE_PATTERNS = [
|
37
|
-
re.compile(
|
38
|
-
|
39
|
-
),
|
40
|
-
re.compile(r"(?:https?://)?(?:www\.)?youtu\.be/([a-zA-Z0-9_-]{11})"),
|
38
|
+
re.compile(r"(?:https?://)?(?:www\.)?youtube\.com/watch\?v=([\w-]{11})"),
|
39
|
+
re.compile(r"(?:https?://)?(?:www\.)?youtu\.be/([\w-]{11})"),
|
41
40
|
]
|
42
41
|
|
43
42
|
@classmethod
|
44
|
-
def
|
45
|
-
|
46
|
-
return text.strip().startswith("[video]") and bool(
|
47
|
-
VideoElement.PATTERN.match(text.strip())
|
48
|
-
)
|
43
|
+
def match_notion(cls, block: Block) -> bool:
|
44
|
+
return block.type == BlockType.VIDEO and block.video
|
49
45
|
|
50
46
|
@classmethod
|
51
|
-
def
|
52
|
-
"""
|
53
|
-
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
57
|
-
"""Convert markdown video embed to Notion video block."""
|
58
|
-
video_match = VideoElement.PATTERN.match(text.strip())
|
59
|
-
if not video_match:
|
60
|
-
return None
|
61
|
-
|
62
|
-
url = video_match.group(1)
|
63
|
-
caption = video_match.group(2)
|
64
|
-
|
65
|
-
if not url:
|
47
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
48
|
+
"""Convert markdown video syntax to a Notion VideoBlock plus an empty paragraph."""
|
49
|
+
match = cls.PATTERN.match(text.strip())
|
50
|
+
if not match:
|
66
51
|
return None
|
67
52
|
|
68
|
-
|
69
|
-
youtube_id = VideoElement._get_youtube_id(url)
|
70
|
-
if youtube_id:
|
71
|
-
url = f"https://www.youtube.com/watch?v={youtube_id}"
|
53
|
+
url, caption_text = match.group(1), match.group(2) or ""
|
72
54
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
if caption:
|
77
|
-
video_data["caption"] = [{"type": "text", "text": {"content": caption}}]
|
78
|
-
else:
|
79
|
-
video_data["caption"] = []
|
55
|
+
vid_id = cls._get_youtube_id(url)
|
56
|
+
if vid_id:
|
57
|
+
url = f"https://www.youtube.com/watch?v={vid_id}"
|
80
58
|
|
81
|
-
|
82
|
-
|
59
|
+
video_block = FileBlock(
|
60
|
+
type=FileType.EXTERNAL, external=ExternalFile(url=url), caption=[]
|
61
|
+
)
|
62
|
+
if caption_text.strip():
|
63
|
+
rt = RichTextObject.from_plain_text(caption_text.strip())
|
64
|
+
video_block.caption = [rt]
|
83
65
|
|
84
|
-
|
85
|
-
empty_paragraph = {"type": "paragraph", "paragraph": {"rich_text": []}}
|
66
|
+
empty_para = ParagraphBlock(rich_text=[])
|
86
67
|
|
87
|
-
return [
|
68
|
+
return [
|
69
|
+
CreateVideoBlock(video=video_block),
|
70
|
+
CreateParagraphBlock(paragraph=empty_para),
|
71
|
+
]
|
88
72
|
|
89
73
|
@classmethod
|
90
|
-
def notion_to_markdown(cls, block:
|
91
|
-
|
92
|
-
if block.get("type") != "video":
|
74
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
75
|
+
if block.type != BlockType.VIDEO or not block.video:
|
93
76
|
return None
|
94
77
|
|
95
|
-
|
96
|
-
|
97
|
-
# Extract URL from video data
|
98
|
-
url = VideoElement._extract_video_url(video_data)
|
99
|
-
if not url:
|
100
|
-
return None
|
78
|
+
fo = block.video
|
101
79
|
|
102
|
-
|
80
|
+
# URL ermitteln
|
81
|
+
if fo.type == FileType.EXTERNAL and fo.external:
|
82
|
+
url = fo.external.url
|
83
|
+
elif fo.type == FileType.FILE and fo.file:
|
84
|
+
url = fo.file.url
|
85
|
+
else:
|
86
|
+
return None # (file_upload o.ä. hier nicht supported)
|
103
87
|
|
104
|
-
|
105
|
-
|
88
|
+
# Captions
|
89
|
+
captions = fo.caption or []
|
90
|
+
if not captions:
|
106
91
|
return f"[video]({url})"
|
107
92
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
return f'[video]({url} "{caption}")'
|
113
|
-
|
114
|
-
return f"[video]({url})"
|
115
|
-
|
116
|
-
@classmethod
|
117
|
-
def is_multiline(cls) -> bool:
|
118
|
-
"""Videos are single-line elements."""
|
119
|
-
return False
|
93
|
+
caption_text = "".join(
|
94
|
+
(rt.plain_text or TextInlineFormatter.extract_text_with_formatting([rt]))
|
95
|
+
for rt in captions
|
96
|
+
)
|
120
97
|
|
121
|
-
|
122
|
-
def _is_youtube_url(cls, url: str) -> bool:
|
123
|
-
"""Check if URL is a YouTube video."""
|
124
|
-
for pattern in VideoElement.YOUTUBE_PATTERNS:
|
125
|
-
if pattern.match(url):
|
126
|
-
return True
|
127
|
-
return False
|
98
|
+
return f'[video]({url} "{caption_text}")'
|
128
99
|
|
129
100
|
@classmethod
|
130
101
|
def _get_youtube_id(cls, url: str) -> Optional[str]:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
return match.group(1)
|
102
|
+
for pat in cls.YOUTUBE_PATTERNS:
|
103
|
+
m = pat.match(url)
|
104
|
+
if m:
|
105
|
+
return m.group(1)
|
136
106
|
return None
|
137
|
-
|
138
|
-
@classmethod
|
139
|
-
def _extract_video_url(cls, video_data: Dict[str, Any]) -> str:
|
140
|
-
"""Extract URL from video data, handling both external and uploaded videos."""
|
141
|
-
if video_data.get("type") == "external":
|
142
|
-
return video_data.get("external", {}).get("url", "")
|
143
|
-
elif video_data.get("type") == "file":
|
144
|
-
return video_data.get("file", {}).get("url", "")
|
145
|
-
return ""
|
146
|
-
|
147
|
-
@classmethod
|
148
|
-
def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
|
149
|
-
"""Extract plain text content from Notion rich_text elements."""
|
150
|
-
result = ""
|
151
|
-
for text_obj in rich_text:
|
152
|
-
if text_obj.get("type") == "text":
|
153
|
-
result += text_obj.get("text", {}).get("content", "")
|
154
|
-
elif "plain_text" in text_obj:
|
155
|
-
result += text_obj.get("plain_text", "")
|
156
|
-
return result
|
157
|
-
|
158
|
-
@classmethod
|
159
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
160
|
-
"""
|
161
|
-
Returns structured LLM prompt metadata for the video element.
|
162
|
-
"""
|
163
|
-
return (
|
164
|
-
ElementPromptBuilder()
|
165
|
-
.with_description(
|
166
|
-
"Embeds video content from external sources like YouTube or direct video URLs."
|
167
|
-
)
|
168
|
-
.with_usage_guidelines(
|
169
|
-
"Use video embeds when you want to include multimedia content directly in your document. "
|
170
|
-
"Videos are useful for tutorials, demonstrations, presentations, or any content that benefits from visual explanation."
|
171
|
-
)
|
172
|
-
.with_syntax('[video](https://example.com/video.mp4 "Optional caption")')
|
173
|
-
.with_examples(
|
174
|
-
[
|
175
|
-
"[video](https://www.youtube.com/watch?v=dQw4w9WgXcQ)",
|
176
|
-
'[video](https://example.com/videos/demo.mp4 "Product demo")',
|
177
|
-
'[video](https://youtu.be/dQw4w9WgXcQ "How to use this feature")',
|
178
|
-
'[video](https://example.com/tutorial.mp4 "Step-by-step tutorial")',
|
179
|
-
]
|
180
|
-
)
|
181
|
-
.build()
|
182
|
-
)
|
@@ -1,8 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from typing import Optional
|
4
|
+
|
4
5
|
from pydantic import BaseModel
|
5
|
-
|
6
|
+
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
8
|
|
7
9
|
|
8
10
|
class VideoMarkdownBlockParams(BaseModel):
|
notionary/database/client.py
CHANGED
@@ -1,19 +1,14 @@
|
|
1
|
-
from typing import
|
2
|
-
from dotenv import load_dotenv
|
3
|
-
from notionary.base_notion_client import BaseNotionClient
|
1
|
+
from typing import Any, Dict, Optional
|
4
2
|
|
5
|
-
from notionary.
|
3
|
+
from notionary.base_notion_client import BaseNotionClient
|
4
|
+
from notionary.database.models import (
|
6
5
|
NotionDatabaseResponse,
|
7
6
|
NotionDatabaseSearchResponse,
|
8
7
|
NotionPageResponse,
|
9
8
|
NotionQueryDatabaseResponse,
|
10
9
|
)
|
11
|
-
from notionary.util import singleton
|
12
|
-
|
13
|
-
load_dotenv()
|
14
10
|
|
15
11
|
|
16
|
-
@singleton
|
17
12
|
class NotionDatabaseClient(BaseNotionClient):
|
18
13
|
"""
|
19
14
|
Specialized Notion client for database operations.
|
notionary/database/database.py
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
import random
|
4
|
-
from typing import Any, AsyncGenerator,
|
5
|
+
from typing import Any, AsyncGenerator, Optional
|
5
6
|
|
6
7
|
from notionary.database.client import NotionDatabaseClient
|
7
|
-
from notionary.
|
8
|
+
from notionary.database.database_filter_builder import DatabaseFilterBuilder
|
9
|
+
from notionary.database.database_provider import NotionDatabaseProvider
|
10
|
+
from notionary.database.models import (
|
8
11
|
NotionDatabaseResponse,
|
9
12
|
NotionPageResponse,
|
10
13
|
NotionQueryDatabaseResponse,
|
11
14
|
)
|
12
15
|
from notionary.page.notion_page import NotionPage
|
13
|
-
|
14
|
-
from notionary.database.database_provider import NotionDatabaseProvider
|
15
|
-
|
16
|
-
from notionary.database.database_filter_builder import DatabaseFilterBuilder
|
17
16
|
from notionary.telemetry import (
|
18
|
-
ProductTelemetry,
|
19
17
|
DatabaseFactoryUsedEvent,
|
18
|
+
ProductTelemetry,
|
20
19
|
QueryOperationEvent,
|
21
20
|
)
|
22
|
-
from notionary.util import
|
21
|
+
from notionary.util import LoggingMixin, factory_only
|
23
22
|
|
24
23
|
|
25
24
|
class NotionDatabase(LoggingMixin):
|
@@ -38,7 +37,7 @@ class NotionDatabase(LoggingMixin):
|
|
38
37
|
title: str,
|
39
38
|
url: str,
|
40
39
|
emoji_icon: Optional[str] = None,
|
41
|
-
properties: Optional[
|
40
|
+
properties: Optional[dict[str, Any]] = None,
|
42
41
|
token: Optional[str] = None,
|
43
42
|
):
|
44
43
|
"""
|
@@ -103,7 +102,7 @@ class NotionDatabase(LoggingMixin):
|
|
103
102
|
return self._emoji_icon
|
104
103
|
|
105
104
|
@property
|
106
|
-
def properties(self) -> Optional[
|
105
|
+
def properties(self) -> Optional[dict[str, Any]]:
|
107
106
|
"""Get the database properties (readonly)."""
|
108
107
|
return self._properties
|
109
108
|
|
@@ -328,7 +327,7 @@ class NotionDatabase(LoggingMixin):
|
|
328
327
|
async def _iter_pages(
|
329
328
|
self,
|
330
329
|
page_size: int = 100,
|
331
|
-
filter_conditions: Optional[
|
330
|
+
filter_conditions: Optional[dict[str, Any]] = None,
|
332
331
|
) -> AsyncGenerator[NotionPage, None]:
|
333
332
|
"""
|
334
333
|
Asynchronous generator that yields NotionPage objects from the database.
|
@@ -422,7 +421,7 @@ class NotionDatabase(LoggingMixin):
|
|
422
421
|
except (KeyError, TypeError, AttributeError):
|
423
422
|
return None
|
424
423
|
|
425
|
-
async def _get_relation_options(self, property_name: str) -> list[
|
424
|
+
async def _get_relation_options(self, property_name: str) -> list[dict[str, Any]]:
|
426
425
|
"""
|
427
426
|
Retrieve the titles of all pages related to a relation property.
|
428
427
|
|
@@ -445,7 +444,7 @@ class NotionDatabase(LoggingMixin):
|
|
445
444
|
async def _paginate_database(
|
446
445
|
self,
|
447
446
|
page_size: int = 100,
|
448
|
-
filter_conditions: Optional[
|
447
|
+
filter_conditions: Optional[dict[str, Any]] = None,
|
449
448
|
) -> AsyncGenerator[list[NotionPageResponse], None]:
|
450
449
|
"""
|
451
450
|
Central pagination logic for Notion Database queries.
|
@@ -461,7 +460,7 @@ class NotionDatabase(LoggingMixin):
|
|
461
460
|
has_more = True
|
462
461
|
|
463
462
|
while has_more:
|
464
|
-
query_data:
|
463
|
+
query_data: dict[str, Any] = {"page_size": page_size}
|
465
464
|
|
466
465
|
if start_cursor:
|
467
466
|
query_data["start_cursor"] = start_cursor
|
@@ -1,10 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
4
|
+
|
4
5
|
from notionary.database.client import NotionDatabaseClient
|
5
6
|
from notionary.database.exceptions import DatabaseNotFoundException
|
6
|
-
from notionary.models
|
7
|
-
from notionary.util import LoggingMixin,
|
7
|
+
from notionary.database.models import NotionDatabaseResponse
|
8
|
+
from notionary.util import LoggingMixin, SingletonMetaClass, format_uuid
|
8
9
|
from notionary.util.fuzzy import find_best_match
|
9
10
|
|
10
11
|
if TYPE_CHECKING:
|
@@ -22,7 +23,7 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
|
|
22
23
|
"""
|
23
24
|
|
24
25
|
def __init__(self):
|
25
|
-
self._database_cache:
|
26
|
+
self._database_cache: dict[str, NotionDatabase] = {}
|
26
27
|
|
27
28
|
async def get_database_by_id(
|
28
29
|
self, database_id: str, token: Optional[str] = None, force_refresh: bool = False
|