notionary 0.3.1__py3-none-any.whl → 0.4.1__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 (201) hide show
  1. notionary/__init__.py +49 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/enums.py +0 -6
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  5. notionary/blocks/rich_text/models.py +13 -4
  6. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  7. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  8. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  9. notionary/blocks/schemas.py +33 -78
  10. notionary/comments/client.py +19 -6
  11. notionary/comments/factory.py +10 -3
  12. notionary/comments/schemas.py +10 -31
  13. notionary/comments/service.py +12 -4
  14. notionary/data_source/http/data_source_instance_client.py +59 -17
  15. notionary/data_source/properties/schemas.py +156 -115
  16. notionary/data_source/query/builder.py +67 -18
  17. notionary/data_source/query/resolver.py +16 -5
  18. notionary/data_source/query/schema.py +24 -6
  19. notionary/data_source/query/validator.py +18 -6
  20. notionary/data_source/schema/registry.py +31 -12
  21. notionary/data_source/schema/service.py +66 -20
  22. notionary/data_source/schemas.py +2 -2
  23. notionary/data_source/service.py +103 -43
  24. notionary/database/client.py +27 -9
  25. notionary/database/database_metadata_update_client.py +12 -4
  26. notionary/database/schemas.py +2 -2
  27. notionary/database/service.py +14 -9
  28. notionary/exceptions/__init__.py +20 -4
  29. notionary/exceptions/api.py +2 -2
  30. notionary/exceptions/base.py +1 -1
  31. notionary/exceptions/block_parsing.py +9 -5
  32. notionary/exceptions/data_source/builder.py +13 -7
  33. notionary/exceptions/data_source/properties.py +6 -4
  34. notionary/exceptions/file_upload.py +76 -0
  35. notionary/exceptions/properties.py +7 -5
  36. notionary/exceptions/search.py +10 -6
  37. notionary/file_upload/__init__.py +4 -0
  38. notionary/file_upload/client.py +128 -210
  39. notionary/file_upload/config/__init__.py +17 -0
  40. notionary/file_upload/config/config.py +39 -0
  41. notionary/file_upload/config/constants.py +16 -0
  42. notionary/file_upload/file/reader.py +28 -0
  43. notionary/file_upload/query/__init__.py +7 -0
  44. notionary/file_upload/query/builder.py +58 -0
  45. notionary/file_upload/query/models.py +37 -0
  46. notionary/file_upload/schemas.py +80 -0
  47. notionary/file_upload/service.py +182 -291
  48. notionary/file_upload/validation/factory.py +66 -0
  49. notionary/file_upload/validation/impl/file_name_length.py +25 -0
  50. notionary/file_upload/validation/models.py +134 -0
  51. notionary/file_upload/validation/port.py +7 -0
  52. notionary/file_upload/validation/service.py +17 -0
  53. notionary/file_upload/validation/validators/__init__.py +11 -0
  54. notionary/file_upload/validation/validators/file_exists.py +15 -0
  55. notionary/file_upload/validation/validators/file_extension.py +131 -0
  56. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  57. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  58. notionary/http/client.py +33 -30
  59. notionary/page/content/__init__.py +9 -0
  60. notionary/page/content/factory.py +21 -7
  61. notionary/page/content/markdown/builder.py +85 -23
  62. notionary/page/content/markdown/nodes/audio.py +8 -4
  63. notionary/page/content/markdown/nodes/base.py +3 -3
  64. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  65. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  66. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  67. notionary/page/content/markdown/nodes/callout.py +2 -2
  68. notionary/page/content/markdown/nodes/code.py +5 -3
  69. notionary/page/content/markdown/nodes/columns.py +3 -3
  70. notionary/page/content/markdown/nodes/container.py +9 -5
  71. notionary/page/content/markdown/nodes/divider.py +2 -2
  72. notionary/page/content/markdown/nodes/embed.py +8 -4
  73. notionary/page/content/markdown/nodes/equation.py +4 -2
  74. notionary/page/content/markdown/nodes/file.py +8 -4
  75. notionary/page/content/markdown/nodes/heading.py +2 -2
  76. notionary/page/content/markdown/nodes/image.py +8 -4
  77. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  78. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  79. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  80. notionary/page/content/markdown/nodes/pdf.py +8 -4
  81. notionary/page/content/markdown/nodes/quote.py +2 -2
  82. notionary/page/content/markdown/nodes/space.py +2 -2
  83. notionary/page/content/markdown/nodes/table.py +8 -5
  84. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  85. notionary/page/content/markdown/nodes/todo.py +15 -7
  86. notionary/page/content/markdown/nodes/toggle.py +2 -2
  87. notionary/page/content/markdown/nodes/video.py +8 -4
  88. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  89. notionary/page/content/markdown/structured_output/models.py +391 -0
  90. notionary/page/content/markdown/structured_output/service.py +211 -0
  91. notionary/page/content/parser/context.py +1 -1
  92. notionary/page/content/parser/factory.py +26 -8
  93. notionary/page/content/parser/parsers/audio.py +12 -32
  94. notionary/page/content/parser/parsers/base.py +2 -2
  95. notionary/page/content/parser/parsers/bookmark.py +2 -2
  96. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  97. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  98. notionary/page/content/parser/parsers/callout.py +15 -5
  99. notionary/page/content/parser/parsers/caption.py +9 -3
  100. notionary/page/content/parser/parsers/code.py +21 -7
  101. notionary/page/content/parser/parsers/column.py +8 -4
  102. notionary/page/content/parser/parsers/column_list.py +19 -7
  103. notionary/page/content/parser/parsers/divider.py +2 -2
  104. notionary/page/content/parser/parsers/embed.py +2 -4
  105. notionary/page/content/parser/parsers/equation.py +8 -4
  106. notionary/page/content/parser/parsers/file.py +12 -34
  107. notionary/page/content/parser/parsers/file_like_block.py +109 -0
  108. notionary/page/content/parser/parsers/heading.py +31 -10
  109. notionary/page/content/parser/parsers/image.py +12 -34
  110. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  111. notionary/page/content/parser/parsers/paragraph.py +3 -1
  112. notionary/page/content/parser/parsers/pdf.py +12 -34
  113. notionary/page/content/parser/parsers/quote.py +28 -9
  114. notionary/page/content/parser/parsers/space.py +2 -2
  115. notionary/page/content/parser/parsers/table.py +31 -10
  116. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  117. notionary/page/content/parser/parsers/todo.py +15 -5
  118. notionary/page/content/parser/parsers/toggle.py +15 -5
  119. notionary/page/content/parser/parsers/video.py +12 -34
  120. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  121. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  122. notionary/page/content/parser/post_processing/service.py +3 -1
  123. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  124. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  125. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  126. notionary/page/content/parser/service.py +4 -1
  127. notionary/page/content/renderer/context.py +15 -5
  128. notionary/page/content/renderer/factory.py +12 -6
  129. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  130. notionary/page/content/renderer/renderers/audio.py +20 -23
  131. notionary/page/content/renderer/renderers/base.py +3 -3
  132. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  133. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  134. notionary/page/content/renderer/renderers/callout.py +19 -7
  135. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  136. notionary/page/content/renderer/renderers/code.py +6 -2
  137. notionary/page/content/renderer/renderers/column.py +3 -1
  138. notionary/page/content/renderer/renderers/column_list.py +3 -1
  139. notionary/page/content/renderer/renderers/embed.py +3 -1
  140. notionary/page/content/renderer/renderers/equation.py +3 -1
  141. notionary/page/content/renderer/renderers/file.py +20 -23
  142. notionary/page/content/renderer/renderers/file_like_block.py +47 -0
  143. notionary/page/content/renderer/renderers/heading.py +22 -8
  144. notionary/page/content/renderer/renderers/image.py +20 -23
  145. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  146. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  147. notionary/page/content/renderer/renderers/pdf.py +20 -23
  148. notionary/page/content/renderer/renderers/quote.py +14 -6
  149. notionary/page/content/renderer/renderers/table.py +15 -5
  150. notionary/page/content/renderer/renderers/todo.py +16 -6
  151. notionary/page/content/renderer/renderers/toggle.py +8 -4
  152. notionary/page/content/renderer/renderers/video.py +20 -23
  153. notionary/page/content/renderer/service.py +9 -3
  154. notionary/page/content/service.py +21 -7
  155. notionary/page/content/syntax/definition/__init__.py +11 -0
  156. notionary/page/content/syntax/definition/models.py +57 -0
  157. notionary/page/content/syntax/definition/registry.py +371 -0
  158. notionary/page/content/syntax/prompts/__init__.py +4 -0
  159. notionary/page/content/syntax/prompts/models.py +11 -0
  160. notionary/page/content/syntax/prompts/registry.py +703 -0
  161. notionary/page/page_metadata_update_client.py +12 -4
  162. notionary/page/properties/client.py +46 -16
  163. notionary/page/properties/factory.py +6 -2
  164. notionary/page/properties/{models.py → schemas.py} +93 -107
  165. notionary/page/properties/service.py +111 -37
  166. notionary/page/schemas.py +3 -3
  167. notionary/page/service.py +21 -7
  168. notionary/shared/entity/client.py +6 -2
  169. notionary/shared/entity/dto_parsers.py +4 -37
  170. notionary/shared/entity/entity_metadata_update_client.py +25 -5
  171. notionary/shared/entity/schemas.py +6 -6
  172. notionary/shared/entity/service.py +89 -35
  173. notionary/shared/models/file.py +36 -6
  174. notionary/shared/models/icon.py +5 -12
  175. notionary/user/base.py +6 -2
  176. notionary/user/bot.py +22 -14
  177. notionary/user/client.py +3 -1
  178. notionary/user/person.py +3 -1
  179. notionary/user/schemas.py +3 -1
  180. notionary/user/service.py +6 -2
  181. notionary/utils/decorators.py +13 -9
  182. notionary/utils/fuzzy.py +6 -2
  183. notionary/utils/mixins/logging.py +3 -1
  184. notionary/utils/pagination.py +14 -4
  185. notionary/workspace/__init__.py +6 -2
  186. notionary/workspace/query/__init__.py +2 -1
  187. notionary/workspace/query/service.py +42 -13
  188. notionary/workspace/service.py +74 -46
  189. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
  190. notionary-0.4.1.dist-info/RECORD +236 -0
  191. notionary/file_upload/models.py +0 -69
  192. notionary/page/blocks/client.py +0 -1
  193. notionary/page/content/syntax/__init__.py +0 -4
  194. notionary/page/content/syntax/models.py +0 -66
  195. notionary/page/content/syntax/registry.py +0 -393
  196. notionary/page/page_context.py +0 -50
  197. notionary/shared/models/cover.py +0 -20
  198. notionary-0.3.1.dist-info/RECORD +0 -211
  199. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  200. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
  201. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from notionary.page.content.markdown.builder import MarkdownBuilder
4
+ from notionary.page.content.markdown.structured_output.models import (
5
+ AudioSchema,
6
+ BookmarkSchema,
7
+ BreadcrumbSchema,
8
+ BulletedListItemSchema,
9
+ BulletedListSchema,
10
+ CalloutSchema,
11
+ CodeSchema,
12
+ ColumnsSchema,
13
+ EmbedSchema,
14
+ EquationSchema,
15
+ FileSchema,
16
+ Heading1Schema,
17
+ Heading2Schema,
18
+ Heading3Schema,
19
+ ImageSchema,
20
+ MarkdownDocumentSchema,
21
+ MarkdownNodeSchema,
22
+ MermaidSchema,
23
+ NumberedListItemSchema,
24
+ NumberedListSchema,
25
+ ParagraphSchema,
26
+ PdfSchema,
27
+ QuoteSchema,
28
+ TableOfContentsSchema,
29
+ TableSchema,
30
+ TodoListSchema,
31
+ TodoSchema,
32
+ ToggleSchema,
33
+ VideoSchema,
34
+ )
35
+ from notionary.utils.decorators import time_execution_sync
36
+ from notionary.utils.mixins.logging import LoggingMixin
37
+
38
+
39
+ class StructuredOutputMarkdownConverter(LoggingMixin):
40
+ def __init__(self, builder: MarkdownBuilder | None = None) -> None:
41
+ self.builder = builder or MarkdownBuilder()
42
+
43
+ @time_execution_sync()
44
+ def convert(self, schema: MarkdownDocumentSchema) -> str:
45
+ for node in schema.nodes:
46
+ self._process_node(node)
47
+ return self.builder.build()
48
+
49
+ def _process_node(self, node: MarkdownNodeSchema) -> None:
50
+ node.process_with(self)
51
+
52
+ def _process_heading_1(self, node: Heading1Schema) -> None:
53
+ builder_func = (
54
+ self._create_children_builder(node.children) if node.children else None
55
+ )
56
+ self.builder.h1(node.text, builder_func)
57
+
58
+ def _process_heading_2(self, node: Heading2Schema) -> None:
59
+ builder_func = (
60
+ self._create_children_builder(node.children) if node.children else None
61
+ )
62
+ self.builder.h2(node.text, builder_func)
63
+
64
+ def _process_heading_3(self, node: Heading3Schema) -> None:
65
+ builder_func = (
66
+ self._create_children_builder(node.children) if node.children else None
67
+ )
68
+ self.builder.h3(node.text, builder_func)
69
+
70
+ def _process_paragraph(self, node: ParagraphSchema) -> None:
71
+ self.builder.paragraph(node.text)
72
+
73
+ def _process_space(self) -> None:
74
+ self.builder.space()
75
+
76
+ def _process_divider(self) -> None:
77
+ self.builder.divider()
78
+
79
+ def _process_quote(self, node: QuoteSchema) -> None:
80
+ builder_func = (
81
+ self._create_children_builder(node.children) if node.children else None
82
+ )
83
+ self.builder.quote(node.text, builder_func)
84
+
85
+ def _process_bulleted_list(self, node: BulletedListSchema) -> None:
86
+ has_children = any(item.children for item in node.items)
87
+
88
+ if has_children:
89
+ for item in node.items:
90
+ self._process_bulleted_list_item(item)
91
+ else:
92
+ texts = [item.text for item in node.items]
93
+ self.builder.bulleted_list(texts)
94
+
95
+ def _process_bulleted_list_item(self, node: BulletedListItemSchema) -> None:
96
+ builder_func = (
97
+ self._create_children_builder(node.children) if node.children else None
98
+ )
99
+ self.builder.bulleted_list_item(node.text, builder_func)
100
+
101
+ def _process_numbered_list(self, node: NumberedListSchema) -> None:
102
+ has_children = any(item.children for item in node.items)
103
+
104
+ if has_children:
105
+ for item in node.items:
106
+ self._process_numbered_list_item(item)
107
+ else:
108
+ texts = [item.text for item in node.items]
109
+ self.builder.numbered_list(texts)
110
+
111
+ def _process_numbered_list_item(self, node: NumberedListItemSchema) -> None:
112
+ builder_func = (
113
+ self._create_children_builder(node.children) if node.children else None
114
+ )
115
+ self.builder.numbered_list_item(node.text, builder_func)
116
+
117
+ def _process_todo(self, node: TodoSchema) -> None:
118
+ builder_func = (
119
+ self._create_children_builder(node.children) if node.children else None
120
+ )
121
+ self.builder.todo(node.text, checked=node.checked, builder_func=builder_func)
122
+
123
+ def _process_todo_list(self, node: TodoListSchema) -> None:
124
+ has_children = any(item.children for item in node.items)
125
+
126
+ if has_children:
127
+ for todo_item in node.items:
128
+ self._process_todo(todo_item)
129
+ else:
130
+ texts = [item.text for item in node.items]
131
+ completed = [item.checked for item in node.items]
132
+ self.builder.todo_list(texts, completed)
133
+
134
+ def _process_callout(self, node: CalloutSchema) -> None:
135
+ if node.children:
136
+ builder_func = self._create_children_builder(node.children)
137
+ self.builder.callout_with_children(node.text, node.emoji, builder_func)
138
+ else:
139
+ self.builder.callout(node.text, node.emoji)
140
+
141
+ def _process_toggle(self, node: ToggleSchema) -> None:
142
+ builder_func = self._create_children_builder(node.children)
143
+ self.builder.toggle(node.title, builder_func)
144
+
145
+ def _process_image(self, node: ImageSchema) -> None:
146
+ self.builder.image(node.url, node.caption)
147
+
148
+ def _process_video(self, node: VideoSchema) -> None:
149
+ self.builder.video(node.url, node.caption)
150
+
151
+ def _process_audio(self, node: AudioSchema) -> None:
152
+ self.builder.audio(node.url, node.caption)
153
+
154
+ def _process_file(self, node: FileSchema) -> None:
155
+ self.builder.file(node.url, node.caption)
156
+
157
+ def _process_pdf(self, node: PdfSchema) -> None:
158
+ self.builder.pdf(node.url, node.caption)
159
+
160
+ def _process_bookmark(self, node: BookmarkSchema) -> None:
161
+ self.builder.bookmark(node.url, node.title, node.caption)
162
+
163
+ def _process_embed(self, node: EmbedSchema) -> None:
164
+ self.builder.embed(node.url, node.caption)
165
+
166
+ def _process_code(self, node: CodeSchema) -> None:
167
+ self.builder.code(node.code, node.language, node.caption)
168
+
169
+ def _process_mermaid(self, node: MermaidSchema) -> None:
170
+ self.builder.mermaid(node.diagram, node.caption)
171
+
172
+ def _process_table(self, node: TableSchema) -> None:
173
+ self.builder.table(node.headers, node.rows)
174
+
175
+ def _process_breadcrumb(self, node: BreadcrumbSchema) -> None:
176
+ self.builder.breadcrumb()
177
+
178
+ def _process_equation(self, node: EquationSchema) -> None:
179
+ self.builder.equation(node.expression)
180
+
181
+ def _process_table_of_contents(self, node: TableOfContentsSchema) -> None:
182
+ self.builder.table_of_contents()
183
+
184
+ def _process_columns(self, node: ColumnsSchema) -> None:
185
+ builder_funcs = []
186
+ width_ratios = []
187
+
188
+ for column in node.columns:
189
+ builder_func = self._create_children_builder(column.children)
190
+ builder_funcs.append(builder_func)
191
+ width_ratios.append(column.width_ratio)
192
+
193
+ if any(r is not None for r in width_ratios):
194
+ self.builder.columns(*builder_funcs, width_ratios=width_ratios)
195
+ else:
196
+ self.builder.columns(*builder_funcs)
197
+
198
+ def _create_children_builder(self, children: list[MarkdownNodeSchema] | None):
199
+ if not children:
200
+ return None
201
+
202
+ captured_children = children
203
+
204
+ def builder_func(builder: MarkdownBuilder) -> MarkdownBuilder:
205
+ converter = StructuredOutputMarkdownConverter()
206
+ converter.builder = builder
207
+ for child in captured_children:
208
+ converter._process_node(child)
209
+ return builder
210
+
211
+ return builder_func
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Awaitable, Callable
4
4
 
5
5
  from notionary.blocks.schemas import BlockCreatePayload
6
- from notionary.page.content.syntax.grammar import MarkdownGrammar
6
+ from notionary.page.content.syntax.definition.grammar import MarkdownGrammar
7
7
 
8
8
 
9
9
  class ParentBlockContext:
@@ -1,6 +1,7 @@
1
1
  from notionary.blocks.rich_text.markdown_rich_text_converter import (
2
2
  MarkdownRichTextConverter,
3
3
  )
4
+ from notionary.file_upload.service import NotionFileUpload
4
5
  from notionary.page.content.parser.parsers import (
5
6
  AudioParser,
6
7
  BookmarkParser,
@@ -29,17 +30,19 @@ from notionary.page.content.parser.parsers import (
29
30
  ToggleParser,
30
31
  VideoParser,
31
32
  )
32
- from notionary.page.content.syntax import SyntaxRegistry
33
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
33
34
 
34
35
 
35
36
  class ConverterChainFactory:
36
37
  def __init__(
37
38
  self,
38
39
  rich_text_converter: MarkdownRichTextConverter | None = None,
39
- syntax_registry: SyntaxRegistry | None = None,
40
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
41
+ file_upload_service: NotionFileUpload | None = None,
40
42
  ) -> None:
41
43
  self._rich_text_converter = rich_text_converter or MarkdownRichTextConverter()
42
- self._syntax_registry = syntax_registry or SyntaxRegistry()
44
+ self._syntax_registry = syntax_registry or SyntaxDefinitionRegistry()
45
+ self._file_upload_service = file_upload_service
43
46
 
44
47
  def create(self) -> LineParser:
45
48
  # multi-line (structural) blocks
@@ -186,19 +189,34 @@ class ConverterChainFactory:
186
189
  return EmbedParser(syntax_registry=self._syntax_registry)
187
190
 
188
191
  def _create_image_parser(self) -> ImageParser:
189
- return ImageParser(syntax_registry=self._syntax_registry)
192
+ return ImageParser(
193
+ syntax_registry=self._syntax_registry,
194
+ file_upload_service=self._file_upload_service,
195
+ )
190
196
 
191
197
  def _create_video_parser(self) -> VideoParser:
192
- return VideoParser(syntax_registry=self._syntax_registry)
198
+ return VideoParser(
199
+ syntax_registry=self._syntax_registry,
200
+ file_upload_service=self._file_upload_service,
201
+ )
193
202
 
194
203
  def _create_audio_parser(self) -> AudioParser:
195
- return AudioParser(syntax_registry=self._syntax_registry)
204
+ return AudioParser(
205
+ syntax_registry=self._syntax_registry,
206
+ file_upload_service=self._file_upload_service,
207
+ )
196
208
 
197
209
  def _create_file_parser(self) -> FileParser:
198
- return FileParser(syntax_registry=self._syntax_registry)
210
+ return FileParser(
211
+ syntax_registry=self._syntax_registry,
212
+ file_upload_service=self._file_upload_service,
213
+ )
199
214
 
200
215
  def _create_pdf_parser(self) -> PdfParser:
201
- return PdfParser(syntax_registry=self._syntax_registry)
216
+ return PdfParser(
217
+ syntax_registry=self._syntax_registry,
218
+ file_upload_service=self._file_upload_service,
219
+ )
202
220
 
203
221
  def _create_caption_parser(self) -> CaptionParser:
204
222
  return CaptionParser(
@@ -1,40 +1,20 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import (
4
- CreateAudioBlock,
5
- ExternalFile,
6
- FileData,
7
- FileType,
3
+ from notionary.blocks.schemas import CreateAudioBlock, ExternalFileWithCaption
4
+ from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
5
+ from notionary.page.content.syntax.definition import (
6
+ SyntaxDefinition,
7
+ SyntaxDefinitionRegistry,
8
8
  )
9
- from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
10
- from notionary.page.content.syntax import SyntaxRegistry
11
9
 
12
10
 
13
- class AudioParser(LineParser):
14
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
15
- super().__init__(syntax_registry)
16
- self._syntax = syntax_registry.get_audio_syntax()
17
-
11
+ class AudioParser(FileLikeBlockParser[CreateAudioBlock]):
18
12
  @override
19
- def _can_handle(self, context: BlockParsingContext) -> bool:
20
- if context.is_inside_parent_context():
21
- return False
22
- return self._syntax.regex_pattern.search(context.line) is not None
13
+ def _get_syntax(
14
+ self, syntax_registry: SyntaxDefinitionRegistry
15
+ ) -> SyntaxDefinition:
16
+ return syntax_registry.get_audio_syntax()
23
17
 
24
18
  @override
25
- async def _process(self, context: BlockParsingContext) -> None:
26
- url = self._extract_url(context.line)
27
- if url is None:
28
- return
29
-
30
- audio_data = FileData(
31
- type=FileType.EXTERNAL,
32
- external=ExternalFile(url=url),
33
- caption=[],
34
- )
35
- block = CreateAudioBlock(audio=audio_data)
36
- context.result_blocks.append(block)
37
-
38
- def _extract_url(self, line: str) -> str | None:
39
- match = self._syntax.regex_pattern.search(line)
40
- return match.group(1).strip() if match else None
19
+ def _create_block(self, file_data: ExternalFileWithCaption) -> CreateAudioBlock:
20
+ return CreateAudioBlock(audio=file_data)
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
4
 
5
5
  from notionary.page.content.parser.context import BlockParsingContext
6
- from notionary.page.content.syntax import SyntaxRegistry
6
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
7
7
 
8
8
 
9
9
  class LineParser(ABC):
10
- def __init__(self, syntax_registry: SyntaxRegistry | None = None) -> None:
10
+ def __init__(self, syntax_registry: SyntaxDefinitionRegistry | None = None) -> None:
11
11
  self._next_handler: LineParser | None = None
12
12
  self._syntax_registry = syntax_registry
13
13
 
@@ -4,11 +4,11 @@ from typing import override
4
4
 
5
5
  from notionary.blocks.schemas import BookmarkData, CreateBookmarkBlock
6
6
  from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
7
- from notionary.page.content.syntax import SyntaxRegistry
7
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
8
8
 
9
9
 
10
10
  class BookmarkParser(LineParser):
11
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
11
+ def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
12
12
  super().__init__(syntax_registry)
13
13
  self._syntax = syntax_registry.get_bookmark_syntax()
14
14
 
@@ -5,11 +5,11 @@ from notionary.page.content.parser.parsers.base import (
5
5
  BlockParsingContext,
6
6
  LineParser,
7
7
  )
8
- from notionary.page.content.syntax import SyntaxRegistry
8
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
9
9
 
10
10
 
11
11
  class BreadcrumbParser(LineParser):
12
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
12
+ def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
13
13
  super().__init__(syntax_registry)
14
14
  self._syntax = syntax_registry.get_breadcrumb_syntax()
15
15
 
@@ -3,16 +3,23 @@ from typing import override
3
3
  from notionary.blocks.rich_text.markdown_rich_text_converter import (
4
4
  MarkdownRichTextConverter,
5
5
  )
6
- from notionary.blocks.schemas import CreateBulletedListItemBlock, CreateBulletedListItemData
6
+ from notionary.blocks.schemas import (
7
+ CreateBulletedListItemBlock,
8
+ CreateBulletedListItemData,
9
+ )
7
10
  from notionary.page.content.parser.parsers.base import (
8
11
  BlockParsingContext,
9
12
  LineParser,
10
13
  )
11
- from notionary.page.content.syntax import SyntaxRegistry
14
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
12
15
 
13
16
 
14
17
  class BulletedListParser(LineParser):
15
- def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
18
+ def __init__(
19
+ self,
20
+ syntax_registry: SyntaxDefinitionRegistry,
21
+ rich_text_converter: MarkdownRichTextConverter,
22
+ ) -> None:
16
23
  super().__init__(syntax_registry)
17
24
  self._syntax = syntax_registry.get_bulleted_list_syntax()
18
25
  self._rich_text_converter = rich_text_converter
@@ -35,7 +42,9 @@ class BulletedListParser(LineParser):
35
42
  await self._process_nested_children(block, context)
36
43
  context.result_blocks.append(block)
37
44
 
38
- async def _process_nested_children(self, block: CreateBulletedListItemBlock, context: BlockParsingContext) -> None:
45
+ async def _process_nested_children(
46
+ self, block: CreateBulletedListItemBlock, context: BlockParsingContext
47
+ ) -> None:
39
48
  child_lines = self._collect_child_lines(context)
40
49
  if not child_lines:
41
50
  return
@@ -57,13 +66,17 @@ class BulletedListParser(LineParser):
57
66
  children_text = self._convert_lines_to_text(stripped_lines)
58
67
  return await context.parse_nested_markdown(children_text)
59
68
 
60
- def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
69
+ def _remove_parent_indentation(
70
+ self, lines: list[str], context: BlockParsingContext
71
+ ) -> list[str]:
61
72
  return context.strip_indentation_level(lines, levels=1)
62
73
 
63
74
  def _convert_lines_to_text(self, lines: list[str]) -> str:
64
75
  return "\n".join(lines)
65
76
 
66
- async def _create_bulleted_list_block(self, text: str) -> CreateBulletedListItemBlock | None:
77
+ async def _create_bulleted_list_block(
78
+ self, text: str
79
+ ) -> CreateBulletedListItemBlock | None:
67
80
  content = self._extract_list_content(text)
68
81
  if content is None:
69
82
  return None
@@ -1,20 +1,26 @@
1
1
  import re
2
2
  from typing import override
3
3
 
4
- from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.rich_text.markdown_rich_text_converter import (
5
+ MarkdownRichTextConverter,
6
+ )
5
7
  from notionary.blocks.schemas import CreateCalloutBlock, CreateCalloutData
6
8
  from notionary.page.content.parser.parsers.base import (
7
9
  BlockParsingContext,
8
10
  LineParser,
9
11
  )
10
- from notionary.page.content.syntax import SyntaxRegistry
12
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
11
13
  from notionary.shared.models.icon import EmojiIcon
12
14
 
13
15
 
14
16
  class CalloutParser(LineParser):
15
17
  DEFAULT_EMOJI = "💡"
16
18
 
17
- def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
19
+ def __init__(
20
+ self,
21
+ syntax_registry: SyntaxDefinitionRegistry,
22
+ rich_text_converter: MarkdownRichTextConverter,
23
+ ) -> None:
18
24
  super().__init__(syntax_registry)
19
25
  self._syntax = syntax_registry.get_callout_syntax()
20
26
  self._pattern = self._syntax.regex_pattern
@@ -37,7 +43,9 @@ class CalloutParser(LineParser):
37
43
  else:
38
44
  context.result_blocks.append(block)
39
45
 
40
- async def _process_nested_children(self, block: CreateCalloutBlock, context: BlockParsingContext) -> None:
46
+ async def _process_nested_children(
47
+ self, block: CreateCalloutBlock, context: BlockParsingContext
48
+ ) -> None:
41
49
  child_lines = self._collect_child_lines(context)
42
50
  if not child_lines:
43
51
  return
@@ -59,7 +67,9 @@ class CalloutParser(LineParser):
59
67
  children_text = self._convert_lines_to_text(stripped_lines)
60
68
  return await context.parse_nested_markdown(children_text)
61
69
 
62
- def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
70
+ def _remove_parent_indentation(
71
+ self, lines: list[str], context: BlockParsingContext
72
+ ) -> list[str]:
63
73
  return context.strip_indentation_level(lines, levels=1)
64
74
 
65
75
  def _convert_lines_to_text(self, lines: list[str]) -> str:
@@ -8,11 +8,15 @@ from notionary.page.content.parser.parsers.base import (
8
8
  BlockParsingContext,
9
9
  LineParser,
10
10
  )
11
- from notionary.page.content.syntax import SyntaxRegistry
11
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
12
12
 
13
13
 
14
14
  class CaptionParser(LineParser):
15
- def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
15
+ def __init__(
16
+ self,
17
+ syntax_registry: SyntaxDefinitionRegistry,
18
+ rich_text_converter: MarkdownRichTextConverter,
19
+ ) -> None:
16
20
  super().__init__(syntax_registry)
17
21
  self._syntax = syntax_registry.get_caption_syntax()
18
22
  self._rich_text_converter = rich_text_converter
@@ -49,7 +53,9 @@ class CaptionParser(LineParser):
49
53
  return False
50
54
  return hasattr(block_data, "caption")
51
55
 
52
- def _attach_caption_to_block(self, block: BlockCreatePayload, caption_rich_text: list) -> None:
56
+ def _attach_caption_to_block(
57
+ self, block: BlockCreatePayload, caption_rich_text: list
58
+ ) -> None:
53
59
  block_data = getattr(block, block.type.value)
54
60
  if hasattr(block_data, "caption"):
55
61
  block_data.caption = caption_rich_text
@@ -1,22 +1,30 @@
1
1
  import re
2
2
  from typing import override
3
3
 
4
- from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.rich_text.markdown_rich_text_converter import (
5
+ MarkdownRichTextConverter,
6
+ )
5
7
  from notionary.blocks.rich_text.models import RichText
6
8
  from notionary.blocks.schemas import CodeData, CodingLanguage, CreateCodeBlock
7
9
  from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
8
- from notionary.page.content.syntax import SyntaxRegistry
10
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
9
11
 
10
12
 
11
13
  class CodeParser(LineParser):
12
14
  DEFAULT_LANGUAGE = CodingLanguage.PLAIN_TEXT
13
15
 
14
- def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
16
+ def __init__(
17
+ self,
18
+ syntax_registry: SyntaxDefinitionRegistry,
19
+ rich_text_converter: MarkdownRichTextConverter,
20
+ ) -> None:
15
21
  super().__init__(syntax_registry)
16
22
  self._syntax = syntax_registry.get_code_syntax()
17
23
  self._rich_text_converter = rich_text_converter
18
24
  self._code_start_pattern = self._syntax.regex_pattern
19
- self._code_end_pattern = self._syntax.end_regex_pattern or re.compile(r"^```\s*$")
25
+ self._code_end_pattern = self._syntax.end_regex_pattern or re.compile(
26
+ r"^```\s*$"
27
+ )
20
28
 
21
29
  @override
22
30
  def _can_handle(self, context: BlockParsingContext) -> bool:
@@ -29,7 +37,9 @@ class CodeParser(LineParser):
29
37
  code_lines = self._collect_code_lines(context)
30
38
  lines_consumed = self._count_lines_consumed(context)
31
39
 
32
- block = await self._create_code_block(opening_line=context.line, code_lines=code_lines)
40
+ block = await self._create_code_block(
41
+ opening_line=context.line, code_lines=code_lines
42
+ )
33
43
  if not block:
34
44
  return
35
45
 
@@ -56,7 +66,9 @@ class CodeParser(LineParser):
56
66
  return line_index + 1
57
67
  return len(context.get_remaining_lines())
58
68
 
59
- async def _create_code_block(self, opening_line: str, code_lines: list[str]) -> CreateCodeBlock | None:
69
+ async def _create_code_block(
70
+ self, opening_line: str, code_lines: list[str]
71
+ ) -> CreateCodeBlock | None:
60
72
  match = self._code_start_pattern.match(opening_line)
61
73
  if not match:
62
74
  return None
@@ -70,7 +82,9 @@ class CodeParser(LineParser):
70
82
  def _parse_language(self, language_str: str | None) -> CodingLanguage:
71
83
  return CodingLanguage.from_string(language_str, default=self.DEFAULT_LANGUAGE)
72
84
 
73
- async def _create_rich_text_from_code(self, code_lines: list[str]) -> list[RichText]:
85
+ async def _create_rich_text_from_code(
86
+ self, code_lines: list[str]
87
+ ) -> list[RichText]:
74
88
  content = "\n".join(code_lines) if code_lines else ""
75
89
  return await self._rich_text_converter.to_rich_text(content)
76
90
 
@@ -5,14 +5,14 @@ from notionary.page.content.parser.parsers.base import (
5
5
  BlockParsingContext,
6
6
  LineParser,
7
7
  )
8
- from notionary.page.content.syntax import SyntaxRegistry
8
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
9
9
 
10
10
 
11
11
  class ColumnParser(LineParser):
12
12
  MIN_WIDTH_RATIO = 0
13
13
  MAX_WIDTH_RATIO = 1.0
14
14
 
15
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
15
+ def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
16
16
  super().__init__(syntax_registry)
17
17
  self._syntax = syntax_registry.get_column_syntax()
18
18
 
@@ -59,7 +59,9 @@ class ColumnParser(LineParser):
59
59
  def _is_valid_width_ratio(self, width_ratio: float) -> bool:
60
60
  return self.MIN_WIDTH_RATIO < width_ratio <= self.MAX_WIDTH_RATIO
61
61
 
62
- async def _populate_children(self, block: CreateColumnBlock, context: BlockParsingContext) -> None:
62
+ async def _populate_children(
63
+ self, block: CreateColumnBlock, context: BlockParsingContext
64
+ ) -> None:
63
65
  parent_indent_level = context.get_line_indentation_level()
64
66
  child_lines = context.collect_indented_child_lines(parent_indent_level)
65
67
 
@@ -70,7 +72,9 @@ class ColumnParser(LineParser):
70
72
  block.column.children = child_blocks
71
73
  context.lines_consumed = len(child_lines)
72
74
 
73
- async def _parse_indented_children(self, child_lines: list[str], context: BlockParsingContext) -> list:
75
+ async def _parse_indented_children(
76
+ self, child_lines: list[str], context: BlockParsingContext
77
+ ) -> list:
74
78
  stripped_lines = context.strip_indentation_level(child_lines, levels=1)
75
79
  child_markdown = "\n".join(stripped_lines)
76
80
  return await context.parse_nested_markdown(child_markdown)