notionary 0.2.19__py3-none-any.whl → 0.2.22__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 +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- 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 +55 -53
- 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 +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -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 +18 -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 +65 -111
- 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 +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- 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 +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- 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 +40 -66
- 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 +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- 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 +68 -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 +52 -92
- 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 +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -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 +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -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 +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -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 +48 -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 +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -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 +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -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 +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -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 +23 -95
- 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 +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -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/models.py +0 -713
- 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/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/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- 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/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /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/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -1,182 +1,111 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
4
|
+
from typing import Optional
|
3
5
|
|
4
|
-
from notionary.blocks import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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.mixins.captions import CaptionMixin
|
9
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
10
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
11
|
+
from notionary.blocks.types import BlockType
|
12
|
+
from notionary.blocks.video.video_element_models import CreateVideoBlock
|
10
13
|
|
11
14
|
|
12
|
-
class VideoElement(
|
15
|
+
class VideoElement(BaseBlockElement, CaptionMixin):
|
13
16
|
"""
|
14
17
|
Handles conversion between Markdown video embeds and Notion video blocks.
|
15
18
|
|
16
19
|
Markdown video syntax:
|
17
|
-
- [video](https://example.com/video.mp4) -
|
18
|
-
- [video](https://example.com/video.mp4
|
19
|
-
|
20
|
-
Where:
|
21
|
-
- URL is the required video URL
|
22
|
-
- Caption is an optional descriptive text (enclosed in quotes)
|
20
|
+
- [video](https://example.com/video.mp4) - URL only
|
21
|
+
- [video](https://example.com/video.mp4)(caption:Demo Video) - URL with caption
|
22
|
+
- (caption:Tutorial video)[video](https://youtube.com/watch?v=abc123) - caption before URL
|
23
23
|
|
24
|
-
Supports
|
24
|
+
Supports YouTube, Vimeo, and direct file URLs.
|
25
25
|
"""
|
26
26
|
|
27
|
-
#
|
28
|
-
|
29
|
-
r"^\[video\]\(" # [video]( prefix
|
30
|
-
+ r'(https?://[^\s"]+)' # URL (required)
|
31
|
-
+ r'(?:\s+"([^"]+)")?' # Optional caption in quotes
|
32
|
-
+ r"\)$" # closing parenthesis
|
33
|
-
)
|
27
|
+
# Flexible pattern that can handle caption in any position
|
28
|
+
VIDEO_PATTERN = re.compile(r"\[video\]\((https?://[^\s\"]+)\)")
|
34
29
|
|
35
|
-
# YouTube URL patterns
|
36
30
|
YOUTUBE_PATTERNS = [
|
37
|
-
re.compile(
|
38
|
-
|
39
|
-
),
|
40
|
-
re.compile(r"(?:https?://)?(?:www\.)?youtu\.be/([a-zA-Z0-9_-]{11})"),
|
31
|
+
re.compile(r"(?:https?://)?(?:www\.)?youtube\.com/watch\?v=([\w-]{11})"),
|
32
|
+
re.compile(r"(?:https?://)?(?:www\.)?youtu\.be/([\w-]{11})"),
|
41
33
|
]
|
42
34
|
|
43
35
|
@classmethod
|
44
|
-
def
|
45
|
-
|
46
|
-
return text.strip().startswith("[video]") and bool(
|
47
|
-
VideoElement.PATTERN.match(text.strip())
|
48
|
-
)
|
36
|
+
def match_notion(cls, block: Block) -> bool:
|
37
|
+
return block.type == BlockType.VIDEO and block.video
|
49
38
|
|
50
39
|
@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())
|
40
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
41
|
+
"""Convert markdown video syntax to a Notion VideoBlock."""
|
42
|
+
# Use our own regex to find the video URL
|
43
|
+
video_match = cls.VIDEO_PATTERN.search(text.strip())
|
59
44
|
if not video_match:
|
60
45
|
return None
|
61
46
|
|
62
47
|
url = video_match.group(1)
|
63
|
-
caption = video_match.group(2)
|
64
|
-
|
65
|
-
if not url:
|
66
|
-
return None
|
67
48
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
url = f"https://www.youtube.com/watch?v={youtube_id}"
|
49
|
+
vid_id = cls._get_youtube_id(url)
|
50
|
+
if vid_id:
|
51
|
+
url = f"https://www.youtube.com/watch?v={vid_id}"
|
72
52
|
|
73
|
-
|
53
|
+
# Use mixin to extract caption (if present anywhere in text)
|
54
|
+
caption_text = cls.extract_caption(text.strip())
|
55
|
+
caption_rich_text = cls.build_caption_rich_text(caption_text or "")
|
74
56
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Prepare the video block
|
82
|
-
video_block = {"type": "video", "video": video_data}
|
83
|
-
|
84
|
-
# Add empty paragraph after video
|
85
|
-
empty_paragraph = {"type": "paragraph", "paragraph": {"rich_text": []}}
|
57
|
+
video_block = FileBlock(
|
58
|
+
type=FileType.EXTERNAL,
|
59
|
+
external=ExternalFile(url=url),
|
60
|
+
caption=caption_rich_text,
|
61
|
+
)
|
86
62
|
|
87
|
-
return
|
63
|
+
return CreateVideoBlock(video=video_block)
|
88
64
|
|
89
65
|
@classmethod
|
90
|
-
def notion_to_markdown(cls, block:
|
91
|
-
|
92
|
-
if block.get("type") != "video":
|
66
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
67
|
+
if block.type != BlockType.VIDEO or not block.video:
|
93
68
|
return None
|
94
69
|
|
95
|
-
|
96
|
-
|
97
|
-
# Extract URL from video data
|
98
|
-
url = VideoElement._extract_video_url(video_data)
|
99
|
-
if not url:
|
100
|
-
return None
|
70
|
+
fo = block.video
|
101
71
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
caption = VideoElement._extract_text_content(caption_rich_text)
|
110
|
-
|
111
|
-
if caption:
|
112
|
-
return f'[video]({url} "{caption}")'
|
72
|
+
# URL ermitteln
|
73
|
+
if fo.type == FileType.EXTERNAL and fo.external:
|
74
|
+
url = fo.external.url
|
75
|
+
elif fo.type == FileType.FILE and fo.file:
|
76
|
+
url = fo.file.url
|
77
|
+
else:
|
78
|
+
return None # (file_upload o.ä. hier nicht supported)
|
113
79
|
|
114
|
-
|
80
|
+
result = f"[video]({url})"
|
115
81
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
82
|
+
# Add caption if present
|
83
|
+
caption_markdown = await cls.format_caption_for_markdown(fo.caption or [])
|
84
|
+
if caption_markdown:
|
85
|
+
result += caption_markdown
|
120
86
|
|
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
|
87
|
+
return result
|
128
88
|
|
129
89
|
@classmethod
|
130
90
|
def _get_youtube_id(cls, url: str) -> Optional[str]:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
return match.group(1)
|
91
|
+
for pat in cls.YOUTUBE_PATTERNS:
|
92
|
+
m = pat.match(url)
|
93
|
+
if m:
|
94
|
+
return m.group(1)
|
136
95
|
return None
|
137
96
|
|
138
97
|
@classmethod
|
139
|
-
def
|
140
|
-
"""
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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()
|
98
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
99
|
+
"""Get system prompt information for video blocks."""
|
100
|
+
return BlockElementMarkdownInformation(
|
101
|
+
block_type=cls.__name__,
|
102
|
+
description="Video blocks embed videos from external URLs like YouTube, Vimeo, or direct video files",
|
103
|
+
syntax_examples=[
|
104
|
+
"[video](https://youtube.com/watch?v=abc123)",
|
105
|
+
"[video](https://vimeo.com/123456789)",
|
106
|
+
"[video](https://example.com/video.mp4)(caption:Demo Video)",
|
107
|
+
"(caption:Tutorial)[video](https://youtu.be/abc123)",
|
108
|
+
"[video](https://youtube.com/watch?v=xyz)(caption:**Important** tutorial)",
|
109
|
+
],
|
110
|
+
usage_guidelines="Use for embedding videos from supported platforms or direct video file URLs. Supports YouTube, Vimeo, and direct video files. Caption supports rich text formatting and describes the video content.",
|
182
111
|
)
|
@@ -1,8 +1,11 @@
|
|
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
|
8
|
+
from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
|
6
9
|
|
7
10
|
|
8
11
|
class VideoMarkdownBlockParams(BaseModel):
|
@@ -10,10 +13,9 @@ class VideoMarkdownBlockParams(BaseModel):
|
|
10
13
|
caption: Optional[str] = None
|
11
14
|
|
12
15
|
|
13
|
-
class VideoMarkdownNode(MarkdownNode):
|
16
|
+
class VideoMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
|
14
17
|
"""
|
15
18
|
Programmatic interface for creating Notion-style video blocks.
|
16
|
-
Example: [video](https://example.com/video.mp4 "Optional caption")
|
17
19
|
"""
|
18
20
|
|
19
21
|
def __init__(self, url: str, caption: Optional[str] = None):
|
@@ -25,6 +27,11 @@ class VideoMarkdownNode(MarkdownNode):
|
|
25
27
|
return cls(url=params.url, caption=params.caption)
|
26
28
|
|
27
29
|
def to_markdown(self) -> str:
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
"""Return the Markdown representation.
|
31
|
+
|
32
|
+
Examples:
|
33
|
+
- [video](https://example.com/movie.mp4)
|
34
|
+
- [video](https://www.youtube.com/watch?v=dQw4w9WgXcQ)(caption:Music Video)
|
35
|
+
"""
|
36
|
+
base_markdown = f"[video]({self.url})"
|
37
|
+
return self.append_caption_to_markdown(base_markdown, self.caption)
|
notionary/database/client.py
CHANGED
@@ -1,19 +1,16 @@
|
|
1
|
-
from typing import
|
2
|
-
|
3
|
-
from
|
1
|
+
from typing import Any, Dict, Optional
|
2
|
+
|
3
|
+
from urllib3.util import response
|
4
4
|
|
5
|
-
from notionary.
|
5
|
+
from notionary.base_notion_client import BaseNotionClient
|
6
|
+
from notionary.database.models import (
|
6
7
|
NotionDatabaseResponse,
|
7
8
|
NotionDatabaseSearchResponse,
|
8
9
|
NotionPageResponse,
|
9
10
|
NotionQueryDatabaseResponse,
|
10
11
|
)
|
11
|
-
from notionary.util import singleton
|
12
12
|
|
13
|
-
load_dotenv()
|
14
13
|
|
15
|
-
|
16
|
-
@singleton
|
17
14
|
class NotionDatabaseClient(BaseNotionClient):
|
18
15
|
"""
|
19
16
|
Specialized Notion client for database operations.
|
@@ -23,6 +20,27 @@ class NotionDatabaseClient(BaseNotionClient):
|
|
23
20
|
def __init__(self, token: Optional[str] = None, timeout: int = 30):
|
24
21
|
super().__init__(token, timeout)
|
25
22
|
|
23
|
+
async def create_database(
|
24
|
+
self,
|
25
|
+
title: str,
|
26
|
+
parent_page_id: Optional[str],
|
27
|
+
properties: Optional[Dict[str, Any]] = None,
|
28
|
+
) -> NotionDatabaseResponse:
|
29
|
+
"""
|
30
|
+
Creates a new database as child of the specified page.
|
31
|
+
"""
|
32
|
+
if properties is None:
|
33
|
+
properties = {"Name": {"title": {}}}
|
34
|
+
|
35
|
+
database_data = {
|
36
|
+
"parent": {"page_id": parent_page_id},
|
37
|
+
"title": [{"type": "text", "text": {"content": title}}],
|
38
|
+
"properties": properties,
|
39
|
+
}
|
40
|
+
|
41
|
+
response = await self.post("databases", database_data)
|
42
|
+
return NotionDatabaseResponse.model_validate(response)
|
43
|
+
|
26
44
|
async def get_database(self, database_id: str) -> NotionDatabaseResponse:
|
27
45
|
"""
|
28
46
|
Gets metadata for a Notion database by its ID.
|
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
|