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
notionary/__init__.py CHANGED
@@ -1,9 +1,13 @@
1
- from .database import NotionDatabase, DatabaseFilterBuilder
1
+ from notionary.blocks import bootstrap_blocks
2
+
3
+ bootstrap_blocks()
4
+
5
+ from .database import DatabaseFilterBuilder, NotionDatabase
6
+ from .file_upload import NotionFileUpload
7
+ from .markdown.markdown_builder import MarkdownBuilder
2
8
  from .page.notion_page import NotionPage
9
+ from .user import NotionBotUser, NotionUser, NotionUserManager
3
10
  from .workspace import NotionWorkspace
4
- from .user import NotionUser, NotionUserManager, NotionBotUser
5
- from .file_upload import NotionFileUpload
6
- from .blocks.markdown_builder import MarkdownBuilder
7
11
 
8
12
  __all__ = [
9
13
  "NotionDatabase",
@@ -2,9 +2,11 @@ import asyncio
2
2
  import os
3
3
  from abc import ABC
4
4
  from enum import Enum
5
- from typing import Dict, Any, Optional, Union
5
+ from typing import Any, Dict, Optional, Union
6
+
6
7
  import httpx
7
8
  from dotenv import load_dotenv
9
+
8
10
  from notionary.util import LoggingMixin
9
11
 
10
12
  load_dotenv()
@@ -1,92 +1,3 @@
1
- # Order is important here, as some imports depend on others.
2
- from .prompts.element_prompt_content import ElementPromptContent
3
- from .prompts.element_prompt_builder import ElementPromptBuilder
1
+ from ._bootstrap import bootstrap_blocks
4
2
 
5
- from .shared.notion_block_element import (
6
- NotionBlockElement,
7
- NotionBlockResult,
8
- NotionBlock,
9
- )
10
-
11
- from .audio import AudioElement, AudioMarkdownNode
12
- from .bulleted_list import BulletedListElement, BulletedListMarkdownNode
13
- from .callout import CalloutElement, CalloutMarkdownNode
14
- from .code import CodeElement, CodeMarkdownNode
15
- from .column.column_element import ColumnElement
16
- from .divider import DividerElement, DividerMarkdownNode
17
- from .embed import EmbedElement, EmbedMarkdownNode
18
- from .heading import HeadingElement, HeadingMarkdownNode
19
- from .image import ImageElement, ImageMarkdownNode
20
- from .numbered_list import NumberedListElement, NumberedListMarkdownNode
21
- from .paragraph import ParagraphElement, ParagraphMarkdownNode
22
- from .table import TableElement, TableMarkdownNode
23
- from .toggle import ToggleElement, ToggleMarkdownNode
24
- from .todo import TodoElement, TodoMarkdownNode
25
- from .video import VideoElement, VideoMarkdownNode
26
- from .toggleable_heading import ToggleableHeadingElement, ToggleableHeadingMarkdownNode
27
- from .bookmark import BookmarkElement, BookmarkMarkdownNode
28
- from .divider import DividerElement, DividerMarkdownNode
29
- from .heading import HeadingElement, HeadingMarkdownNode
30
- from .mention import MentionElement, MentionMarkdownNode
31
- from .quote import QuoteElement, QuoteMarkdownNode
32
- from .document import DocumentElement, DocumentMarkdownNode
33
- from .shared.text_inline_formatter import TextInlineFormatter
34
-
35
- from .markdown_node import MarkdownNode
36
-
37
- from .registry.block_registry import BlockRegistry
38
- from .registry.block_registry_builder import BlockRegistryBuilder
39
-
40
- from .shared.block_client import NotionBlockClient
41
-
42
- __all__ = [
43
- "MarkdownNode",
44
- "ElementPromptContent",
45
- "ElementPromptBuilder",
46
- "NotionBlockElement",
47
- "AudioElement",
48
- "AudioMarkdownNode",
49
- "BulletedListElement",
50
- "BulletedListMarkdownNode",
51
- "CalloutElement",
52
- "CalloutMarkdownNode",
53
- "CodeElement",
54
- "CodeMarkdownNode",
55
- "ColumnElement",
56
- "DividerElement",
57
- "DividerMarkdownNode",
58
- "EmbedElement",
59
- "EmbedMarkdownNode",
60
- "HeadingElement",
61
- "HeadingMarkdownNode",
62
- "ImageElement",
63
- "ImageMarkdownNode",
64
- "NumberedListElement",
65
- "NumberedListMarkdownNode",
66
- "ParagraphElement",
67
- "ParagraphMarkdownNode",
68
- "TableElement",
69
- "TableMarkdownNode",
70
- "ToggleElement",
71
- "ToggleMarkdownNode",
72
- "TodoElement",
73
- "TodoMarkdownNode",
74
- "VideoElement",
75
- "VideoMarkdownNode",
76
- "ToggleableHeadingElement",
77
- "ToggleableHeadingMarkdownNode",
78
- "BookmarkElement",
79
- "BookmarkMarkdownNode",
80
- "MentionElement",
81
- "MentionMarkdownNode",
82
- "QuoteElement",
83
- "QuoteMarkdownNode",
84
- "DocumentElement",
85
- "DocumentMarkdownNode",
86
- "BlockRegistry",
87
- "BlockRegistryBuilder",
88
- "TextInlineFormatter",
89
- "NotionBlockResult",
90
- "NotionBlock",
91
- "NotionBlockClient",
92
- ]
3
+ __all__ = ["bootstrap_blocks"]
@@ -0,0 +1,271 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, Union
4
+
5
+ _bootstrapped = False
6
+
7
+
8
+ def bootstrap_blocks() -> None:
9
+ global _bootstrapped
10
+ if _bootstrapped:
11
+ return
12
+
13
+ from notionary.blocks import (
14
+ bookmark,
15
+ breadcrumbs,
16
+ bulleted_list,
17
+ callout,
18
+ child_page,
19
+ code,
20
+ column,
21
+ divider,
22
+ embed,
23
+ equation,
24
+ file,
25
+ heading,
26
+ image_block,
27
+ models,
28
+ numbered_list,
29
+ paragraph,
30
+ quote,
31
+ table,
32
+ table_of_contents,
33
+ todo,
34
+ toggle,
35
+ toggleable_heading,
36
+ video,
37
+ child_database,
38
+ )
39
+
40
+ # Collect all exports from modules
41
+ ns = {}
42
+ for m in (
43
+ bookmark,
44
+ breadcrumbs,
45
+ bulleted_list,
46
+ callout,
47
+ child_page,
48
+ code,
49
+ column,
50
+ divider,
51
+ embed,
52
+ equation,
53
+ file,
54
+ heading,
55
+ image_block,
56
+ numbered_list,
57
+ paragraph,
58
+ quote,
59
+ table,
60
+ todo,
61
+ toggle,
62
+ video,
63
+ toggleable_heading,
64
+ table_of_contents,
65
+ child_database,
66
+ ):
67
+ ns.update(vars(m))
68
+
69
+ # Add missing types that are needed for model rebuilding
70
+ # These are the types that are only defined in TYPE_CHECKING in block_models
71
+ from notionary.blocks.bookmark.bookmark_models import (
72
+ BookmarkBlock,
73
+ CreateBookmarkBlock,
74
+ )
75
+ from notionary.blocks.breadcrumbs.breadcrumb_models import (
76
+ BreadcrumbBlock,
77
+ CreateBreadcrumbBlock,
78
+ )
79
+ from notionary.blocks.bulleted_list.bulleted_list_models import (
80
+ BulletedListItemBlock,
81
+ CreateBulletedListItemBlock,
82
+ )
83
+ from notionary.blocks.callout.callout_models import CalloutBlock, CreateCalloutBlock
84
+ from notionary.blocks.child_page.child_page_models import (
85
+ ChildPageBlock,
86
+ CreateChildPageBlock,
87
+ )
88
+ from notionary.blocks.code.code_models import CodeBlock, CreateCodeBlock
89
+ from notionary.blocks.column.column_models import (
90
+ ColumnBlock,
91
+ ColumnListBlock,
92
+ CreateColumnBlock,
93
+ CreateColumnListBlock,
94
+ )
95
+ from notionary.blocks.divider.divider_models import CreateDividerBlock, DividerBlock
96
+ from notionary.blocks.embed.embed_models import CreateEmbedBlock, EmbedBlock
97
+ from notionary.blocks.equation.equation_models import (
98
+ CreateEquationBlock,
99
+ EquationBlock,
100
+ )
101
+ from notionary.blocks.file.file_element_models import CreateFileBlock, FileBlock
102
+ from notionary.blocks.heading.heading_models import (
103
+ CreateHeading1Block,
104
+ CreateHeading2Block,
105
+ CreateHeading3Block,
106
+ HeadingBlock,
107
+ )
108
+ from notionary.blocks.image_block.image_models import CreateImageBlock
109
+ from notionary.blocks.numbered_list.numbered_list_models import (
110
+ CreateNumberedListItemBlock,
111
+ NumberedListItemBlock,
112
+ )
113
+ from notionary.blocks.paragraph.paragraph_models import (
114
+ CreateParagraphBlock,
115
+ ParagraphBlock,
116
+ )
117
+ from notionary.blocks.pdf.pdf_models import CreatePdfBlock
118
+ from notionary.blocks.quote.quote_models import CreateQuoteBlock, QuoteBlock
119
+ from notionary.blocks.table.table_models import TableBlock, TableRowBlock
120
+ from notionary.blocks.table_of_contents.table_of_contents_models import (
121
+ CreateTableOfContentsBlock,
122
+ TableOfContentsBlock,
123
+ )
124
+ from notionary.blocks.todo.todo_models import CreateToDoBlock, ToDoBlock
125
+ from notionary.blocks.toggle.toggle_models import CreateToggleBlock, ToggleBlock
126
+ from notionary.blocks.types import BlockType
127
+ from notionary.blocks.video.video_element_models import CreateVideoBlock
128
+ from notionary.blocks.child_database.child_database_models import (
129
+ CreateChildDatabaseBlock,
130
+ ChildDatabaseBlock,
131
+ )
132
+
133
+ # Define the Union types that are needed for model rebuilding
134
+ BlockCreateRequest = Union[
135
+ CreateBookmarkBlock,
136
+ CreateBreadcrumbBlock,
137
+ CreateBulletedListItemBlock,
138
+ CreateCalloutBlock,
139
+ CreateChildPageBlock,
140
+ CreateCodeBlock,
141
+ CreateColumnListBlock,
142
+ CreateColumnBlock,
143
+ CreateDividerBlock,
144
+ CreateEmbedBlock,
145
+ CreateEquationBlock,
146
+ CreateFileBlock,
147
+ CreateHeading1Block,
148
+ CreateHeading2Block,
149
+ CreateHeading3Block,
150
+ CreateImageBlock,
151
+ CreateNumberedListItemBlock,
152
+ CreateParagraphBlock,
153
+ CreateQuoteBlock,
154
+ CreateToDoBlock,
155
+ CreateToggleBlock,
156
+ CreateVideoBlock,
157
+ CreateTableOfContentsBlock,
158
+ CreatePdfBlock,
159
+ CreateChildDatabaseBlock,
160
+ ]
161
+
162
+ BlockCreateResult = Optional[BlockCreateRequest]
163
+
164
+ # Add all block types to namespace
165
+ ns.update(
166
+ {
167
+ "BlockType": BlockType,
168
+ "BookmarkBlock": BookmarkBlock,
169
+ "CreateBookmarkBlock": CreateBookmarkBlock,
170
+ "BreadcrumbBlock": BreadcrumbBlock,
171
+ "CreateBreadcrumbBlock": CreateBreadcrumbBlock,
172
+ "BulletedListItemBlock": BulletedListItemBlock,
173
+ "CreateBulletedListItemBlock": CreateBulletedListItemBlock,
174
+ "CalloutBlock": CalloutBlock,
175
+ "CreateCalloutBlock": CreateCalloutBlock,
176
+ "ChildPageBlock": ChildPageBlock,
177
+ "CreateChildPageBlock": CreateChildPageBlock,
178
+ "CodeBlock": CodeBlock,
179
+ "CreateCodeBlock": CreateCodeBlock,
180
+ "ColumnBlock": ColumnBlock,
181
+ "ColumnListBlock": ColumnListBlock,
182
+ "CreateColumnBlock": CreateColumnBlock,
183
+ "CreateColumnListBlock": CreateColumnListBlock,
184
+ "DividerBlock": DividerBlock,
185
+ "CreateDividerBlock": CreateDividerBlock,
186
+ "EmbedBlock": EmbedBlock,
187
+ "CreateEmbedBlock": CreateEmbedBlock,
188
+ "EquationBlock": EquationBlock,
189
+ "CreateEquationBlock": CreateEquationBlock,
190
+ "FileBlock": FileBlock,
191
+ "CreateFileBlock": CreateFileBlock,
192
+ "HeadingBlock": HeadingBlock,
193
+ "CreateHeading1Block": CreateHeading1Block,
194
+ "CreateHeading2Block": CreateHeading2Block,
195
+ "CreateHeading3Block": CreateHeading3Block,
196
+ "CreateImageBlock": CreateImageBlock,
197
+ "NumberedListItemBlock": NumberedListItemBlock,
198
+ "CreateNumberedListItemBlock": CreateNumberedListItemBlock,
199
+ "ParagraphBlock": ParagraphBlock,
200
+ "CreateParagraphBlock": CreateParagraphBlock,
201
+ "QuoteBlock": QuoteBlock,
202
+ "CreateQuoteBlock": CreateQuoteBlock,
203
+ "TableBlock": TableBlock,
204
+ "TableRowBlock": TableRowBlock,
205
+ "ToDoBlock": ToDoBlock,
206
+ "CreateToDoBlock": CreateToDoBlock,
207
+ "ToggleBlock": ToggleBlock,
208
+ "CreateToggleBlock": CreateToggleBlock,
209
+ "CreateVideoBlock": CreateVideoBlock,
210
+ "TableOfContentsBlock": TableOfContentsBlock,
211
+ "CreateTableOfContentsBlock": CreateTableOfContentsBlock,
212
+ "ChildDatabaseBlock": ChildDatabaseBlock,
213
+ # Add the Union types
214
+ "BlockCreateRequest": BlockCreateRequest,
215
+ "BlockCreateResult": BlockCreateResult,
216
+ }
217
+ )
218
+
219
+ # Now rebuild with complete namespace
220
+ models.Block.model_rebuild(_types_namespace=ns)
221
+ models.BlockChildrenResponse.model_rebuild(_types_namespace=ns)
222
+
223
+ # Rebuild all individual block models
224
+ BookmarkBlock.model_rebuild()
225
+ BreadcrumbBlock.model_rebuild()
226
+ BulletedListItemBlock.model_rebuild()
227
+ CalloutBlock.model_rebuild()
228
+ ChildPageBlock.model_rebuild()
229
+ CodeBlock.model_rebuild()
230
+ ColumnBlock.model_rebuild()
231
+ ColumnListBlock.model_rebuild()
232
+ DividerBlock.model_rebuild()
233
+ EmbedBlock.model_rebuild()
234
+ EquationBlock.model_rebuild()
235
+ FileBlock.model_rebuild()
236
+ HeadingBlock.model_rebuild()
237
+ NumberedListItemBlock.model_rebuild()
238
+ ParagraphBlock.model_rebuild()
239
+ QuoteBlock.model_rebuild()
240
+ TableBlock.model_rebuild()
241
+ TableRowBlock.model_rebuild()
242
+ ToDoBlock.model_rebuild()
243
+ ToggleBlock.model_rebuild()
244
+ TableOfContentsBlock.model_rebuild()
245
+
246
+ # Rebuild create models
247
+ CreateBookmarkBlock.model_rebuild()
248
+ CreateBreadcrumbBlock.model_rebuild()
249
+ CreateBulletedListItemBlock.model_rebuild()
250
+ CreateCalloutBlock.model_rebuild()
251
+ CreateChildPageBlock.model_rebuild()
252
+ CreateCodeBlock.model_rebuild()
253
+ CreateColumnListBlock.model_rebuild()
254
+ CreateColumnBlock.model_rebuild()
255
+ CreateDividerBlock.model_rebuild()
256
+ CreateEmbedBlock.model_rebuild()
257
+ CreateEquationBlock.model_rebuild()
258
+ CreateFileBlock.model_rebuild()
259
+ CreateHeading1Block.model_rebuild()
260
+ CreateHeading2Block.model_rebuild()
261
+ CreateHeading3Block.model_rebuild()
262
+ CreateImageBlock.model_rebuild()
263
+ CreateNumberedListItemBlock.model_rebuild()
264
+ CreateParagraphBlock.model_rebuild()
265
+ CreateQuoteBlock.model_rebuild()
266
+ CreateToDoBlock.model_rebuild()
267
+ CreateToggleBlock.model_rebuild()
268
+ CreateVideoBlock.model_rebuild()
269
+ CreateTableOfContentsBlock.model_rebuild()
270
+
271
+ _bootstrapped = True
@@ -1,7 +1,13 @@
1
- from .audio_element import AudioElement
2
- from .audio_markdown_node import AudioMarkdownNode
1
+ from notionary.blocks.audio.audio_element import AudioElement
2
+ from notionary.blocks.audio.audio_markdown_node import (
3
+ AudioMarkdownBlockParams,
4
+ AudioMarkdownNode,
5
+ )
6
+ from notionary.blocks.audio.audio_models import CreateAudioBlock
3
7
 
4
8
  __all__ = [
5
9
  "AudioElement",
10
+ "CreateAudioBlock",
6
11
  "AudioMarkdownNode",
12
+ "AudioMarkdownBlockParams",
7
13
  ]
@@ -1,152 +1,115 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Any, Optional, List
4
+ from typing import Optional
3
5
 
4
- from notionary.blocks import (
5
- NotionBlockElement,
6
- ElementPromptContent,
7
- ElementPromptBuilder,
8
- NotionBlockResult,
9
- )
10
- from notionary.blocks.shared.models import RichTextObject
6
+ from notionary.blocks.audio.audio_models import CreateAudioBlock
7
+ from notionary.blocks.base_block_element import BaseBlockElement
8
+ from notionary.blocks.file.file_element_models import ExternalFile, FileBlock, FileType
9
+ from notionary.blocks.mixins.captions import CaptionMixin
10
+ from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
11
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
11
12
 
12
13
 
13
- class AudioElement(NotionBlockElement):
14
+ class AudioElement(BaseBlockElement, CaptionMixin):
14
15
  """
15
16
  Handles conversion between Markdown audio embeds and Notion audio blocks.
16
17
 
17
18
  Markdown audio syntax:
18
19
  - [audio](https://example.com/audio.mp3) - Simple audio embed
19
- - [audio](https://example.com/audio.mp3 "Caption text") - Audio with caption
20
+ - [audio](https://example.com/audio.mp3)(caption:Episode 1) - Audio with caption
21
+ - (caption:Background music)[audio](https://example.com/song.mp3) - caption before URL
20
22
 
21
23
  Where:
22
24
  - URL is the required audio file URL
23
- - Caption is optional descriptive text (enclosed in quotes)
25
+ - Caption supports rich text formatting and is optional
24
26
  """
25
27
 
26
- # Regex patterns
27
- URL_PATTERN = r"(https?://[^\s\"]+)"
28
- CAPTION_PATTERN = r'(?:\s+"([^"]+)")?'
28
+ # Simple pattern that matches just the audio link, CaptionMixin handles caption separately
29
+ AUDIO_PATTERN = re.compile(r"\[audio\]\((https?://[^\s\"]+)\)")
29
30
 
30
- PATTERN = re.compile(r"^\[audio\]\(" + URL_PATTERN + CAPTION_PATTERN + r"\)$")
31
+ @classmethod
32
+ def _extract_audio_url(cls, text: str) -> Optional[str]:
33
+ """Extract audio URL from text, handling caption patterns."""
34
+ # First remove any captions to get clean text for URL extraction
35
+ clean_text = cls.remove_caption(text)
31
36
 
32
- # Supported audio extensions
33
- SUPPORTED_EXTENSIONS = {".mp3", ".wav", ".ogg", ".oga", ".m4a"}
37
+ # Now extract the URL from clean text
38
+ match = cls.AUDIO_PATTERN.search(clean_text)
39
+ if match:
40
+ return match.group(1)
34
41
 
35
- @classmethod
36
- def match_markdown(cls, text: str) -> bool:
37
- m = cls.PATTERN.match(text.strip())
38
- if not m:
39
- return False
40
- url = m.group(1)
41
- return cls._is_likely_audio_url(url)
42
+ return None
43
+
44
+ SUPPORTED_EXTENSIONS = {".mp3", ".wav", ".ogg", ".oga", ".m4a"}
42
45
 
43
46
  @classmethod
44
- def match_notion(cls, block: dict[str, Any]) -> bool:
45
- """Check if block is a Notion audio block."""
46
- return block.get("type") == "audio"
47
+ def match_notion(cls, block: Block) -> bool:
48
+ """Check if this element can handle the given Notion block."""
49
+ return block.type == BlockType.AUDIO
47
50
 
48
51
  @classmethod
49
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
52
+ async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
50
53
  """Convert markdown audio embed to Notion audio block."""
51
- audio_match = cls.PATTERN.match(text.strip())
52
- if not audio_match:
53
- return None
54
-
55
- url = audio_match.group(1)
56
- caption_text = audio_match.group(2)
57
-
54
+ # Use our helper method to extract the URL
55
+ url = cls._extract_audio_url(text.strip())
58
56
  if not url:
59
57
  return None
60
58
 
61
- # Validate URL if possible
62
59
  if not cls._is_likely_audio_url(url):
63
- # Still proceed - user might know better
64
- pass
60
+ return None
65
61
 
66
- audio_data = {"type": "external", "external": {"url": url}}
62
+ # Use mixin to extract caption (if present anywhere in text)
63
+ caption_text = cls.extract_caption(text.strip())
64
+ caption_rich_text = cls.build_caption_rich_text(caption_text or "")
67
65
 
68
- # Add caption if provided
69
- if caption_text:
70
- caption_rich_text = RichTextObject.from_plain_text(caption_text)
71
- audio_data["caption"] = [caption_rich_text.model_dump()]
72
- else:
73
- audio_data["caption"] = []
66
+ audio_content = FileBlock(
67
+ type=FileType.EXTERNAL,
68
+ external=ExternalFile(url=url),
69
+ caption=caption_rich_text,
70
+ )
74
71
 
75
- return {"type": "audio", "audio": audio_data}
72
+ return CreateAudioBlock(audio=audio_content)
76
73
 
77
74
  @classmethod
78
- def notion_to_markdown(cls, block: dict[str, Any]) -> Optional[str]:
75
+ async def notion_to_markdown(cls, block: Block) -> Optional[str]:
79
76
  """Convert Notion audio block to markdown audio embed."""
80
- if block.get("type") != "audio":
77
+ if block.type != BlockType.AUDIO or block.audio is None:
81
78
  return None
82
79
 
83
- audio_data = block.get("audio", {})
80
+ audio = block.audio
84
81
 
85
- # Get URL from external source
86
- if audio_data.get("type") == "external":
87
- url = audio_data.get("external", {}).get("url", "")
88
- else:
89
- # Handle file or file_upload types if needed
82
+ # Only handle external audio
83
+ if audio.type != FileType.EXTERNAL or audio.external is None:
90
84
  return None
91
-
85
+ url = audio.external.url
92
86
  if not url:
93
87
  return None
94
88
 
95
- # Extract caption
96
- caption = audio_data.get("caption", [])
97
- if caption:
98
- caption_text = cls._extract_text_content(caption)
99
- return f'[audio]({url} "{caption_text}")'
89
+ result = f"[audio]({url})"
90
+
91
+ # Add caption if present
92
+ caption_markdown = await cls.format_caption_for_markdown(audio.caption or [])
93
+ if caption_markdown:
94
+ result += caption_markdown
100
95
 
101
- return f"[audio]({url})"
96
+ return result
102
97
 
103
98
  @classmethod
104
- def is_multiline(cls) -> bool:
105
- """Audio embeds are single-line elements."""
106
- return False
99
+ def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
100
+ """Get system prompt information for audio blocks."""
101
+ return BlockElementMarkdownInformation(
102
+ block_type=cls.__name__,
103
+ description="Audio blocks embed audio files from external URLs with optional captions",
104
+ syntax_examples=[
105
+ "[audio](https://example.com/song.mp3)",
106
+ "[audio](https://example.com/podcast.wav)(caption:Episode 1)",
107
+ "(caption:Background music)[audio](https://soundcloud.com/track/123)",
108
+ "[audio](https://example.com/interview.mp3)(caption:**Live** interview)",
109
+ ],
110
+ usage_guidelines="Use for embedding audio files like music, podcasts, or sound effects. Supports common audio formats (mp3, wav, ogg, m4a). Caption supports rich text formatting and is optional.",
111
+ )
107
112
 
108
113
  @classmethod
109
114
  def _is_likely_audio_url(cls, url: str) -> bool:
110
- """Check if URL likely points to an audio file."""
111
115
  return any(url.lower().endswith(ext) for ext in cls.SUPPORTED_EXTENSIONS)
112
-
113
- @classmethod
114
- def _extract_text_content(cls, rich_text: List[dict[str, Any]]) -> str:
115
- """Extract plain text content from Notion rich_text elements."""
116
- result = ""
117
- for text_obj in rich_text:
118
- if text_obj.get("type") == "text":
119
- result += text_obj.get("text", {}).get("content", "")
120
- elif "plain_text" in text_obj:
121
- result += text_obj.get("plain_text", "")
122
- return result
123
-
124
- @classmethod
125
- def get_llm_prompt_content(cls) -> ElementPromptContent:
126
- """
127
- Returns structured LLM prompt metadata for the audio element.
128
- """
129
- return (
130
- ElementPromptBuilder()
131
- .with_description(
132
- "Embeds an audio file that can be played directly in the page."
133
- )
134
- .with_usage_guidelines(
135
- "Use audio embeds when you want to include sound files, music, podcasts, "
136
- "or voice recordings. Supports common audio formats like MP3, WAV, OGG, and M4A."
137
- )
138
- .with_syntax('![audio](https://example.com/audio.mp3 "Optional caption")')
139
- .with_examples(
140
- [
141
- "[audio](https://example.com/song.mp3)",
142
- '[audio](https://example.com/podcast.mp3 "Episode 1: Introduction")',
143
- '[audio](https://example.com/sound.wav "Sound effect for presentation")',
144
- '[audio](https://example.com/recording.m4a "Voice memo from meeting")',
145
- ]
146
- )
147
- .with_avoidance_guidelines(
148
- "Ensure the URL points to a valid audio file. "
149
- "Some audio formats may not be supported by all browsers."
150
- )
151
- .build()
152
- )
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import Optional
4
+
3
5
  from pydantic import BaseModel
4
6
 
5
- from notionary.blocks.markdown_node import MarkdownNode
7
+ from notionary.markdown.markdown_node import MarkdownNode
8
+ from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
6
9
 
7
10
 
8
11
  class AudioMarkdownBlockParams(BaseModel):
@@ -10,7 +13,7 @@ class AudioMarkdownBlockParams(BaseModel):
10
13
  caption: Optional[str] = None
11
14
 
12
15
 
13
- class AudioMarkdownNode(MarkdownNode):
16
+ class AudioMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
14
17
  """
15
18
  Programmatic interface for creating Notion-style audio blocks.
16
19
  """
@@ -24,6 +27,11 @@ class AudioMarkdownNode(MarkdownNode):
24
27
  return cls(url=params.url, caption=params.caption)
25
28
 
26
29
  def to_markdown(self) -> str:
27
- if self.caption:
28
- return f'[audio]({self.url} "{self.caption}")'
29
- return f"[audio]({self.url})"
30
+ """Return the Markdown representation.
31
+
32
+ Examples:
33
+ - [audio](https://example.com/song.mp3)
34
+ - [audio](https://example.com/song.mp3)(caption:Background music)
35
+ """
36
+ base_markdown = f"[audio]({self.url})"
37
+ return self.append_caption_to_markdown(base_markdown, self.caption)