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.
Files changed (220) hide show
  1. notionary/__init__.py +8 -4
  2. notionary/base_notion_client.py +3 -1
  3. notionary/blocks/__init__.py +2 -91
  4. notionary/blocks/_bootstrap.py +271 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +69 -106
  7. notionary/blocks/audio/audio_markdown_node.py +13 -5
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +42 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +49 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
  13. notionary/blocks/bookmark/bookmark_models.py +15 -0
  14. notionary/blocks/breadcrumbs/__init__.py +17 -0
  15. notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
  16. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
  17. notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
  18. notionary/blocks/bulleted_list/__init__.py +12 -2
  19. notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
  20. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
  21. notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
  22. notionary/blocks/callout/__init__.py +9 -2
  23. notionary/blocks/callout/callout_element.py +53 -86
  24. notionary/blocks/callout/callout_markdown_node.py +3 -1
  25. notionary/blocks/callout/callout_models.py +33 -0
  26. notionary/blocks/child_database/__init__.py +14 -0
  27. notionary/blocks/child_database/child_database_element.py +61 -0
  28. notionary/blocks/child_database/child_database_models.py +12 -0
  29. notionary/blocks/child_page/__init__.py +9 -0
  30. notionary/blocks/child_page/child_page_element.py +94 -0
  31. notionary/blocks/child_page/child_page_models.py +12 -0
  32. notionary/blocks/{shared/block_client.py → client.py} +54 -54
  33. notionary/blocks/code/__init__.py +6 -2
  34. notionary/blocks/code/code_element.py +96 -181
  35. notionary/blocks/code/code_markdown_node.py +64 -13
  36. notionary/blocks/code/code_models.py +94 -0
  37. notionary/blocks/column/__init__.py +25 -1
  38. notionary/blocks/column/column_element.py +44 -312
  39. notionary/blocks/column/column_list_element.py +52 -0
  40. notionary/blocks/column/column_list_markdown_node.py +50 -0
  41. notionary/blocks/column/column_markdown_node.py +59 -0
  42. notionary/blocks/column/column_models.py +26 -0
  43. notionary/blocks/divider/__init__.py +9 -2
  44. notionary/blocks/divider/divider_element.py +18 -49
  45. notionary/blocks/divider/divider_markdown_node.py +2 -1
  46. notionary/blocks/divider/divider_models.py +12 -0
  47. notionary/blocks/embed/__init__.py +9 -2
  48. notionary/blocks/embed/embed_element.py +65 -111
  49. notionary/blocks/embed/embed_markdown_node.py +3 -1
  50. notionary/blocks/embed/embed_models.py +14 -0
  51. notionary/blocks/equation/__init__.py +14 -0
  52. notionary/blocks/equation/equation_element.py +133 -0
  53. notionary/blocks/equation/equation_element_markdown_node.py +35 -0
  54. notionary/blocks/equation/equation_models.py +11 -0
  55. notionary/blocks/file/__init__.py +25 -0
  56. notionary/blocks/file/file_element.py +112 -0
  57. notionary/blocks/file/file_element_markdown_node.py +37 -0
  58. notionary/blocks/file/file_element_models.py +39 -0
  59. notionary/blocks/guards.py +22 -0
  60. notionary/blocks/heading/__init__.py +16 -2
  61. notionary/blocks/heading/heading_element.py +83 -69
  62. notionary/blocks/heading/heading_markdown_node.py +2 -1
  63. notionary/blocks/heading/heading_models.py +29 -0
  64. notionary/blocks/image_block/__init__.py +13 -0
  65. notionary/blocks/image_block/image_element.py +89 -0
  66. notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
  67. notionary/blocks/image_block/image_models.py +10 -0
  68. notionary/blocks/mixins/captions/__init__.py +4 -0
  69. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
  70. notionary/blocks/mixins/captions/caption_mixin.py +92 -0
  71. notionary/blocks/models.py +174 -0
  72. notionary/blocks/numbered_list/__init__.py +12 -2
  73. notionary/blocks/numbered_list/numbered_list_element.py +48 -56
  74. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  75. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  76. notionary/blocks/paragraph/__init__.py +12 -2
  77. notionary/blocks/paragraph/paragraph_element.py +40 -66
  78. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  79. notionary/blocks/paragraph/paragraph_models.py +16 -0
  80. notionary/blocks/pdf/__init__.py +13 -0
  81. notionary/blocks/pdf/pdf_element.py +97 -0
  82. notionary/blocks/pdf/pdf_markdown_node.py +37 -0
  83. notionary/blocks/pdf/pdf_models.py +11 -0
  84. notionary/blocks/quote/__init__.py +11 -2
  85. notionary/blocks/quote/quote_element.py +45 -62
  86. notionary/blocks/quote/quote_markdown_node.py +6 -3
  87. notionary/blocks/quote/quote_models.py +18 -0
  88. notionary/blocks/registry/__init__.py +4 -0
  89. notionary/blocks/registry/block_registry.py +60 -121
  90. notionary/blocks/registry/block_registry_builder.py +115 -59
  91. notionary/blocks/rich_text/__init__.py +33 -0
  92. notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
  93. notionary/blocks/rich_text/rich_text_models.py +221 -0
  94. notionary/blocks/rich_text/text_inline_formatter.py +456 -0
  95. notionary/blocks/syntax_prompt_builder.py +137 -0
  96. notionary/blocks/table/__init__.py +16 -2
  97. notionary/blocks/table/table_element.py +136 -228
  98. notionary/blocks/table/table_markdown_node.py +2 -1
  99. notionary/blocks/table/table_models.py +28 -0
  100. notionary/blocks/table_of_contents/__init__.py +19 -0
  101. notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
  102. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  103. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  104. notionary/blocks/todo/__init__.py +9 -2
  105. notionary/blocks/todo/todo_element.py +52 -92
  106. notionary/blocks/todo/todo_markdown_node.py +2 -1
  107. notionary/blocks/todo/todo_models.py +19 -0
  108. notionary/blocks/toggle/__init__.py +13 -3
  109. notionary/blocks/toggle/toggle_element.py +69 -260
  110. notionary/blocks/toggle/toggle_markdown_node.py +25 -15
  111. notionary/blocks/toggle/toggle_models.py +17 -0
  112. notionary/blocks/toggleable_heading/__init__.py +6 -2
  113. notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
  114. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  115. notionary/blocks/types.py +130 -0
  116. notionary/blocks/video/__init__.py +8 -2
  117. notionary/blocks/video/video_element.py +70 -141
  118. notionary/blocks/video/video_element_models.py +10 -0
  119. notionary/blocks/video/video_markdown_node.py +13 -6
  120. notionary/database/client.py +26 -8
  121. notionary/database/database.py +13 -14
  122. notionary/database/database_filter_builder.py +2 -2
  123. notionary/database/database_provider.py +5 -4
  124. notionary/database/models.py +337 -0
  125. notionary/database/notion_database.py +6 -7
  126. notionary/file_upload/client.py +5 -7
  127. notionary/file_upload/models.py +3 -2
  128. notionary/file_upload/notion_file_upload.py +2 -3
  129. notionary/markdown/markdown_builder.py +729 -0
  130. notionary/markdown/markdown_document_model.py +228 -0
  131. notionary/{blocks → markdown}/markdown_node.py +1 -0
  132. notionary/models/notion_database_response.py +0 -338
  133. notionary/page/client.py +34 -15
  134. notionary/page/models.py +327 -0
  135. notionary/page/notion_page.py +136 -58
  136. notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
  137. notionary/page/page_content_writer.py +177 -0
  138. notionary/page/page_context.py +65 -0
  139. notionary/page/reader/handler/__init__.py +19 -0
  140. notionary/page/reader/handler/base_block_renderer.py +44 -0
  141. notionary/page/reader/handler/block_processing_context.py +35 -0
  142. notionary/page/reader/handler/block_rendering_context.py +48 -0
  143. notionary/page/reader/handler/column_list_renderer.py +51 -0
  144. notionary/page/reader/handler/column_renderer.py +60 -0
  145. notionary/page/reader/handler/line_renderer.py +73 -0
  146. notionary/page/reader/handler/numbered_list_renderer.py +85 -0
  147. notionary/page/reader/handler/toggle_renderer.py +69 -0
  148. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  149. notionary/page/reader/page_content_retriever.py +81 -0
  150. notionary/page/search_filter_builder.py +2 -1
  151. notionary/page/writer/handler/__init__.py +24 -0
  152. notionary/page/writer/handler/code_handler.py +72 -0
  153. notionary/page/writer/handler/column_handler.py +141 -0
  154. notionary/page/writer/handler/column_list_handler.py +139 -0
  155. notionary/page/writer/handler/equation_handler.py +74 -0
  156. notionary/page/writer/handler/line_handler.py +35 -0
  157. notionary/page/writer/handler/line_processing_context.py +54 -0
  158. notionary/page/writer/handler/regular_line_handler.py +86 -0
  159. notionary/page/writer/handler/table_handler.py +66 -0
  160. notionary/page/writer/handler/toggle_handler.py +155 -0
  161. notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
  162. notionary/page/writer/markdown_to_notion_converter.py +95 -0
  163. notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
  164. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
  165. notionary/page/writer/notion_text_length_processor.py +150 -0
  166. notionary/telemetry/__init__.py +2 -2
  167. notionary/telemetry/service.py +3 -3
  168. notionary/user/__init__.py +2 -2
  169. notionary/user/base_notion_user.py +2 -1
  170. notionary/user/client.py +2 -3
  171. notionary/user/models.py +1 -0
  172. notionary/user/notion_bot_user.py +4 -5
  173. notionary/user/notion_user.py +3 -4
  174. notionary/user/notion_user_manager.py +23 -95
  175. notionary/util/__init__.py +3 -2
  176. notionary/util/fuzzy.py +2 -1
  177. notionary/util/logging_mixin.py +2 -2
  178. notionary/util/singleton_metaclass.py +1 -1
  179. notionary/workspace.py +6 -5
  180. notionary-0.2.22.dist-info/METADATA +237 -0
  181. notionary-0.2.22.dist-info/RECORD +200 -0
  182. notionary/blocks/document/__init__.py +0 -7
  183. notionary/blocks/document/document_element.py +0 -102
  184. notionary/blocks/document/document_markdown_node.py +0 -31
  185. notionary/blocks/image/__init__.py +0 -7
  186. notionary/blocks/image/image_element.py +0 -151
  187. notionary/blocks/markdown_builder.py +0 -356
  188. notionary/blocks/mention/__init__.py +0 -7
  189. notionary/blocks/mention/mention_element.py +0 -229
  190. notionary/blocks/mention/mention_markdown_node.py +0 -38
  191. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  192. notionary/blocks/prompts/element_prompt_content.py +0 -41
  193. notionary/blocks/shared/models.py +0 -713
  194. notionary/blocks/shared/notion_block_element.py +0 -37
  195. notionary/blocks/shared/text_inline_formatter.py +0 -262
  196. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  197. notionary/database/models/page_result.py +0 -10
  198. notionary/models/notion_block_response.py +0 -264
  199. notionary/models/notion_page_response.py +0 -78
  200. notionary/models/search_response.py +0 -0
  201. notionary/page/__init__.py +0 -0
  202. notionary/page/content/markdown_whitespace_processor.py +0 -80
  203. notionary/page/content/notion_text_length_utils.py +0 -87
  204. notionary/page/content/page_content_retriever.py +0 -60
  205. notionary/page/formatting/line_processor.py +0 -153
  206. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  207. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  208. notionary/page/notion_to_markdown_converter.py +0 -179
  209. notionary/page/properites/property_value_extractor.py +0 -0
  210. notionary/user/notion_user_provider.py +0 -1
  211. notionary-0.2.19.dist-info/METADATA +0 -225
  212. notionary-0.2.19.dist-info/RECORD +0 -150
  213. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  214. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  215. /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
  216. /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
  217. /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
  218. /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
  219. {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
  220. {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 Dict, Any, Optional, List
4
+ from typing import Optional
3
5
 
4
- from notionary.blocks import (
5
- ElementPromptContent,
6
- ElementPromptBuilder,
7
- NotionBlockResult,
8
- NotionBlockElement,
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(NotionBlockElement):
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) - Simple video with URL only
18
- - [video](https://example.com/video.mp4 "Caption") - Video with URL and caption
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 various video URLs including YouTube, Vimeo, and direct video file links.
24
+ Supports YouTube, Vimeo, and direct file URLs.
25
25
  """
26
26
 
27
- # Regex pattern for video syntax with optional caption
28
- PATTERN = re.compile(
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
- r"(?:https?://)?(?:www\.)?youtube\.com/watch\?v=([a-zA-Z0-9_-]{11})"
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 match_markdown(cls, text: str) -> bool:
45
- """Check if text is a markdown video embed."""
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 match_notion(cls, block: Dict[str, Any]) -> bool:
52
- """Check if block is a Notion video."""
53
- return block.get("type") == "video"
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
- # Normalize YouTube URLs
69
- youtube_id = VideoElement._get_youtube_id(url)
70
- if youtube_id:
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
- video_data = {"type": "external", "external": {"url": url}}
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
- # Add caption if provided
76
- if caption:
77
- video_data["caption"] = [{"type": "text", "text": {"content": caption}}]
78
- else:
79
- video_data["caption"] = []
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 [video_block, empty_paragraph]
63
+ return CreateVideoBlock(video=video_block)
88
64
 
89
65
  @classmethod
90
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
91
- """Convert Notion video block to markdown video embed."""
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
- video_data = block.get("video", {})
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
- caption_rich_text = video_data.get("caption", [])
103
-
104
- if not caption_rich_text:
105
- # Simple video with URL only
106
- return f"[video]({url})"
107
-
108
- # Extract caption text
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
- return f"[video]({url})"
80
+ result = f"[video]({url})"
115
81
 
116
- @classmethod
117
- def is_multiline(cls) -> bool:
118
- """Videos are single-line elements."""
119
- return False
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
- @classmethod
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
- """Extract YouTube video ID from URL."""
132
- for pattern in VideoElement.YOUTUBE_PATTERNS:
133
- match = pattern.match(url)
134
- if match:
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 _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()
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
  )
@@ -0,0 +1,10 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from notionary.blocks.file.file_element_models import FileBlock
6
+
7
+
8
+ class CreateVideoBlock(BaseModel):
9
+ type: Literal["video"] = "video"
10
+ video: FileBlock
@@ -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
- from notionary.blocks.markdown_node import MarkdownNode
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
- if self.caption:
29
- return f'[video]({self.url} "{self.caption}")'
30
- return f"[video]({self.url})"
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)
@@ -1,19 +1,16 @@
1
- from typing import Dict, Any, Optional
2
- from dotenv import load_dotenv
3
- from notionary.base_notion_client import BaseNotionClient
1
+ from typing import Any, Dict, Optional
2
+
3
+ from urllib3.util import response
4
4
 
5
- from notionary.models.notion_database_response import (
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.
@@ -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, Dict, Optional
5
+ from typing import Any, AsyncGenerator, Optional
5
6
 
6
7
  from notionary.database.client import NotionDatabaseClient
7
- from notionary.models.notion_database_response import (
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 factory_only, LoggingMixin
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[Dict[str, Any]] = None,
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[Dict[str, Any]]:
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[Dict[str, Any]] = None,
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[Dict[str, Any]]:
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[Dict[str, Any]] = None,
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: Dict[str, Any] = {"page_size": page_size}
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,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Dict, List
4
- from datetime import datetime, timedelta
5
3
  from dataclasses import dataclass, field
4
+ from datetime import datetime, timedelta
5
+ from typing import Any, Dict, List
6
6
 
7
7
 
8
8
  @dataclass
@@ -1,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Dict, Optional, TYPE_CHECKING
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.notion_database_response import NotionDatabaseResponse
7
- from notionary.util import LoggingMixin, format_uuid, SingletonMetaClass
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: Dict[str, NotionDatabase] = {}
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