notionary 0.4.0__py3-none-any.whl → 0.4.2__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 (178) hide show
  1. notionary/__init__.py +44 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  4. notionary/blocks/rich_text/models.py +13 -4
  5. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  6. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  7. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  8. notionary/blocks/schemas.py +2 -1
  9. notionary/comments/client.py +19 -6
  10. notionary/comments/factory.py +10 -3
  11. notionary/comments/schemas.py +9 -3
  12. notionary/comments/service.py +12 -4
  13. notionary/data_source/http/data_source_instance_client.py +59 -17
  14. notionary/data_source/properties/schemas.py +30 -10
  15. notionary/data_source/query/builder.py +67 -18
  16. notionary/data_source/query/resolver.py +16 -5
  17. notionary/data_source/query/schema.py +24 -6
  18. notionary/data_source/query/validator.py +18 -6
  19. notionary/data_source/schema/registry.py +31 -12
  20. notionary/data_source/schema/service.py +66 -20
  21. notionary/data_source/service.py +74 -23
  22. notionary/database/client.py +27 -9
  23. notionary/database/database_metadata_update_client.py +12 -4
  24. notionary/database/service.py +11 -4
  25. notionary/exceptions/__init__.py +15 -3
  26. notionary/exceptions/block_parsing.py +6 -2
  27. notionary/exceptions/data_source/builder.py +11 -5
  28. notionary/exceptions/data_source/properties.py +3 -1
  29. notionary/exceptions/file_upload.py +12 -3
  30. notionary/exceptions/properties.py +3 -1
  31. notionary/exceptions/search.py +6 -2
  32. notionary/file_upload/client.py +5 -1
  33. notionary/file_upload/config/config.py +10 -3
  34. notionary/file_upload/query/builder.py +6 -2
  35. notionary/file_upload/schemas.py +3 -1
  36. notionary/file_upload/service.py +42 -14
  37. notionary/file_upload/validation/factory.py +3 -1
  38. notionary/file_upload/validation/impl/file_name_length.py +3 -1
  39. notionary/file_upload/validation/models.py +15 -5
  40. notionary/file_upload/validation/validators/file_extension.py +12 -3
  41. notionary/http/client.py +27 -8
  42. notionary/page/content/__init__.py +9 -0
  43. notionary/page/content/factory.py +21 -7
  44. notionary/page/content/markdown/builder.py +85 -23
  45. notionary/page/content/markdown/nodes/audio.py +8 -4
  46. notionary/page/content/markdown/nodes/base.py +3 -3
  47. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  48. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  49. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  50. notionary/page/content/markdown/nodes/callout.py +2 -2
  51. notionary/page/content/markdown/nodes/code.py +5 -3
  52. notionary/page/content/markdown/nodes/columns.py +3 -3
  53. notionary/page/content/markdown/nodes/container.py +9 -5
  54. notionary/page/content/markdown/nodes/divider.py +2 -2
  55. notionary/page/content/markdown/nodes/embed.py +8 -4
  56. notionary/page/content/markdown/nodes/equation.py +4 -2
  57. notionary/page/content/markdown/nodes/file.py +8 -4
  58. notionary/page/content/markdown/nodes/heading.py +2 -2
  59. notionary/page/content/markdown/nodes/image.py +8 -4
  60. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  61. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  62. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  63. notionary/page/content/markdown/nodes/pdf.py +8 -4
  64. notionary/page/content/markdown/nodes/quote.py +2 -2
  65. notionary/page/content/markdown/nodes/space.py +2 -2
  66. notionary/page/content/markdown/nodes/table.py +8 -5
  67. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  68. notionary/page/content/markdown/nodes/todo.py +15 -7
  69. notionary/page/content/markdown/nodes/toggle.py +2 -2
  70. notionary/page/content/markdown/nodes/video.py +8 -4
  71. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  72. notionary/page/content/markdown/structured_output/models.py +391 -0
  73. notionary/page/content/markdown/structured_output/service.py +211 -0
  74. notionary/page/content/parser/context.py +1 -1
  75. notionary/page/content/parser/factory.py +23 -8
  76. notionary/page/content/parser/parsers/audio.py +7 -2
  77. notionary/page/content/parser/parsers/base.py +2 -2
  78. notionary/page/content/parser/parsers/bookmark.py +2 -2
  79. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  80. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  81. notionary/page/content/parser/parsers/callout.py +15 -5
  82. notionary/page/content/parser/parsers/caption.py +9 -3
  83. notionary/page/content/parser/parsers/code.py +21 -7
  84. notionary/page/content/parser/parsers/column.py +8 -4
  85. notionary/page/content/parser/parsers/column_list.py +19 -7
  86. notionary/page/content/parser/parsers/divider.py +2 -2
  87. notionary/page/content/parser/parsers/embed.py +2 -2
  88. notionary/page/content/parser/parsers/equation.py +8 -4
  89. notionary/page/content/parser/parsers/file.py +7 -2
  90. notionary/page/content/parser/parsers/file_like_block.py +30 -10
  91. notionary/page/content/parser/parsers/heading.py +31 -10
  92. notionary/page/content/parser/parsers/image.py +7 -2
  93. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  94. notionary/page/content/parser/parsers/paragraph.py +3 -1
  95. notionary/page/content/parser/parsers/pdf.py +7 -2
  96. notionary/page/content/parser/parsers/quote.py +28 -9
  97. notionary/page/content/parser/parsers/space.py +2 -2
  98. notionary/page/content/parser/parsers/table.py +31 -10
  99. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  100. notionary/page/content/parser/parsers/todo.py +15 -5
  101. notionary/page/content/parser/parsers/toggle.py +15 -5
  102. notionary/page/content/parser/parsers/video.py +7 -2
  103. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  104. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  105. notionary/page/content/parser/post_processing/service.py +3 -1
  106. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  107. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  108. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  109. notionary/page/content/parser/service.py +4 -1
  110. notionary/page/content/renderer/context.py +15 -5
  111. notionary/page/content/renderer/factory.py +12 -6
  112. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  113. notionary/page/content/renderer/renderers/audio.py +14 -5
  114. notionary/page/content/renderer/renderers/base.py +3 -3
  115. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  116. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  117. notionary/page/content/renderer/renderers/callout.py +19 -7
  118. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  119. notionary/page/content/renderer/renderers/code.py +6 -2
  120. notionary/page/content/renderer/renderers/column.py +3 -1
  121. notionary/page/content/renderer/renderers/column_list.py +3 -1
  122. notionary/page/content/renderer/renderers/embed.py +3 -1
  123. notionary/page/content/renderer/renderers/equation.py +3 -1
  124. notionary/page/content/renderer/renderers/file.py +14 -5
  125. notionary/page/content/renderer/renderers/file_like_block.py +8 -4
  126. notionary/page/content/renderer/renderers/heading.py +22 -8
  127. notionary/page/content/renderer/renderers/image.py +13 -4
  128. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  129. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  130. notionary/page/content/renderer/renderers/pdf.py +14 -5
  131. notionary/page/content/renderer/renderers/quote.py +14 -6
  132. notionary/page/content/renderer/renderers/table.py +15 -5
  133. notionary/page/content/renderer/renderers/todo.py +16 -6
  134. notionary/page/content/renderer/renderers/toggle.py +8 -4
  135. notionary/page/content/renderer/renderers/video.py +14 -5
  136. notionary/page/content/renderer/service.py +9 -3
  137. notionary/page/content/service.py +21 -7
  138. notionary/page/content/syntax/definition/__init__.py +11 -0
  139. notionary/page/content/syntax/definition/models.py +57 -0
  140. notionary/page/content/syntax/definition/registry.py +371 -0
  141. notionary/page/content/syntax/prompts/__init__.py +4 -0
  142. notionary/page/content/syntax/prompts/models.py +11 -0
  143. notionary/page/content/syntax/prompts/registry.py +703 -0
  144. notionary/page/page_metadata_update_client.py +12 -4
  145. notionary/page/properties/client.py +45 -15
  146. notionary/page/properties/factory.py +6 -2
  147. notionary/page/properties/service.py +110 -36
  148. notionary/page/service.py +20 -6
  149. notionary/shared/entity/client.py +6 -2
  150. notionary/shared/entity/dto_parsers.py +3 -1
  151. notionary/shared/entity/entity_metadata_update_client.py +9 -3
  152. notionary/shared/entity/schemas.py +1 -1
  153. notionary/shared/entity/service.py +53 -22
  154. notionary/shared/models/file.py +3 -1
  155. notionary/shared/models/icon.py +6 -4
  156. notionary/user/base.py +6 -2
  157. notionary/user/bot.py +10 -2
  158. notionary/user/client.py +3 -1
  159. notionary/user/person.py +3 -1
  160. notionary/user/schemas.py +3 -1
  161. notionary/user/service.py +6 -2
  162. notionary/utils/decorators.py +6 -2
  163. notionary/utils/fuzzy.py +6 -2
  164. notionary/utils/mixins/logging.py +3 -1
  165. notionary/utils/pagination.py +14 -4
  166. notionary/workspace/__init__.py +5 -1
  167. notionary/workspace/query/service.py +59 -16
  168. notionary/workspace/service.py +39 -11
  169. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/METADATA +1 -1
  170. notionary-0.4.2.dist-info/RECORD +236 -0
  171. notionary/page/blocks/client.py +0 -1
  172. notionary/page/content/syntax/__init__.py +0 -5
  173. notionary/page/content/syntax/models.py +0 -66
  174. notionary/page/content/syntax/registry.py +0 -371
  175. notionary-0.4.0.dist-info/RECORD +0 -230
  176. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  177. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/WHEEL +0 -0
  178. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -6,17 +6,21 @@ from notionary.blocks.schemas import (
6
6
  ExternalFileWithCaption,
7
7
  NotionHostedFileWithCaption,
8
8
  )
9
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
10
- from notionary.page.content.syntax import SyntaxDefinition
9
+ from notionary.page.content.renderer.renderers.captioned_block import (
10
+ CaptionedBlockRenderer,
11
+ )
12
+ from notionary.page.content.syntax.definition import EnclosedSyntaxDefinition
11
13
 
12
14
 
13
15
  class FileLikeBlockRenderer(CaptionedBlockRenderer):
14
16
  @abstractmethod
15
- def _get_syntax(self) -> SyntaxDefinition:
17
+ def _get_syntax(self) -> EnclosedSyntaxDefinition:
16
18
  pass
17
19
 
18
20
  @abstractmethod
19
- def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
21
+ def _get_file_data(
22
+ self, block: Block
23
+ ) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
20
24
  pass
21
25
 
22
26
  @override
@@ -1,10 +1,12 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
4
+ RichTextToMarkdownConverter,
5
+ )
4
6
  from notionary.blocks.schemas import Block, BlockType, HeadingData
5
7
  from notionary.page.content.renderer.context import MarkdownRenderingContext
6
8
  from notionary.page.content.renderer.renderers.base import BlockRenderer
7
- from notionary.page.content.syntax import SyntaxRegistry
9
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
8
10
 
9
11
 
10
12
  class HeadingRenderer(BlockRenderer):
@@ -13,16 +15,22 @@ class HeadingRenderer(BlockRenderer):
13
15
 
14
16
  def __init__(
15
17
  self,
16
- syntax_registry: SyntaxRegistry | None = None,
18
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
17
19
  rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
18
20
  ) -> None:
19
21
  super().__init__(syntax_registry=syntax_registry)
20
22
  self._syntax = self._syntax_registry.get_heading_syntax()
21
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
23
+ self._rich_text_markdown_converter = (
24
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
25
+ )
22
26
 
23
27
  @override
24
28
  def _can_handle(self, block: Block) -> bool:
25
- return block.type in (BlockType.HEADING_1, BlockType.HEADING_2, BlockType.HEADING_3)
29
+ return block.type in (
30
+ BlockType.HEADING_1,
31
+ BlockType.HEADING_2,
32
+ BlockType.HEADING_3,
33
+ )
26
34
 
27
35
  @override
28
36
  async def _process(self, context: MarkdownRenderingContext) -> None:
@@ -35,7 +43,9 @@ class HeadingRenderer(BlockRenderer):
35
43
  heading_markdown = self._format_heading(level, title, context.indent_level)
36
44
 
37
45
  if self._is_toggleable(context.block):
38
- context.markdown_result = await self._render_toggleable_heading(heading_markdown, context)
46
+ context.markdown_result = await self._render_toggleable_heading(
47
+ heading_markdown, context
48
+ )
39
49
  else:
40
50
  context.markdown_result = heading_markdown
41
51
 
@@ -52,7 +62,9 @@ class HeadingRenderer(BlockRenderer):
52
62
 
53
63
  return heading_markdown
54
64
 
55
- async def _render_toggleable_heading(self, heading_markdown: str, context: MarkdownRenderingContext) -> str:
65
+ async def _render_toggleable_heading(
66
+ self, heading_markdown: str, context: MarkdownRenderingContext
67
+ ) -> str:
56
68
  original_indent = context.indent_level
57
69
  context.indent_level += 1
58
70
 
@@ -83,7 +95,9 @@ class HeadingRenderer(BlockRenderer):
83
95
  if not heading_data or not heading_data.rich_text:
84
96
  return ""
85
97
 
86
- return await self._rich_text_markdown_converter.to_markdown(heading_data.rich_text)
98
+ return await self._rich_text_markdown_converter.to_markdown(
99
+ heading_data.rich_text
100
+ )
87
101
 
88
102
  def _get_heading_data(self, block: Block) -> HeadingData | None:
89
103
  if block.type == BlockType.HEADING_1:
@@ -1,8 +1,15 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType, ExternalFileWithCaption, NotionHostedFileWithCaption
4
- from notionary.page.content.renderer.renderers.file_like_block import FileLikeBlockRenderer
5
- from notionary.page.content.syntax import SyntaxDefinition
3
+ from notionary.blocks.schemas import (
4
+ Block,
5
+ BlockType,
6
+ ExternalFileWithCaption,
7
+ NotionHostedFileWithCaption,
8
+ )
9
+ from notionary.page.content.renderer.renderers.file_like_block import (
10
+ FileLikeBlockRenderer,
11
+ )
12
+ from notionary.page.content.syntax.definition import SyntaxDefinition
6
13
 
7
14
 
8
15
  class ImageRenderer(FileLikeBlockRenderer):
@@ -15,5 +22,7 @@ class ImageRenderer(FileLikeBlockRenderer):
15
22
  return self._syntax_registry.get_image_syntax()
16
23
 
17
24
  @override
18
- def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
25
+ def _get_file_data(
26
+ self, block: Block
27
+ ) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
28
  return block.image
@@ -6,18 +6,23 @@ from notionary.blocks.rich_text.rich_text_markdown_converter import (
6
6
  from notionary.blocks.schemas import Block, BlockType
7
7
  from notionary.page.content.renderer.context import MarkdownRenderingContext
8
8
  from notionary.page.content.renderer.renderers.base import BlockRenderer
9
- from notionary.page.content.syntax import MarkdownGrammar, SyntaxRegistry
9
+ from notionary.page.content.syntax.definition import (
10
+ MarkdownGrammar,
11
+ SyntaxDefinitionRegistry,
12
+ )
10
13
 
11
14
 
12
15
  class NumberedListRenderer(BlockRenderer):
13
16
  def __init__(
14
17
  self,
15
- syntax_registry: SyntaxRegistry | None = None,
18
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
16
19
  rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
17
20
  markdown_grammar: MarkdownGrammar | None = None,
18
21
  ) -> None:
19
22
  super().__init__(syntax_registry=syntax_registry)
20
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
23
+ self._rich_text_markdown_converter = (
24
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
25
+ )
21
26
 
22
27
  markdown_grammar = markdown_grammar or MarkdownGrammar()
23
28
  self._numbered_list_placeholder = markdown_grammar.numbered_list_placeholder
@@ -1,15 +1,21 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
4
+ RichTextToMarkdownConverter,
5
+ )
4
6
  from notionary.blocks.schemas import Block, BlockType
5
7
  from notionary.page.content.renderer.context import MarkdownRenderingContext
6
8
  from notionary.page.content.renderer.renderers.base import BlockRenderer
7
9
 
8
10
 
9
11
  class ParagraphRenderer(BlockRenderer):
10
- def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
12
+ def __init__(
13
+ self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None
14
+ ) -> None:
11
15
  super().__init__()
12
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
16
+ self._rich_text_markdown_converter = (
17
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
18
+ )
13
19
 
14
20
  @override
15
21
  def _can_handle(self, block: Block) -> bool:
@@ -37,4 +43,6 @@ class ParagraphRenderer(BlockRenderer):
37
43
  if not block.paragraph or not block.paragraph.rich_text:
38
44
  return None
39
45
 
40
- return await self._rich_text_markdown_converter.to_markdown(block.paragraph.rich_text)
46
+ return await self._rich_text_markdown_converter.to_markdown(
47
+ block.paragraph.rich_text
48
+ )
@@ -1,8 +1,15 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType, ExternalFileWithCaption, NotionHostedFileWithCaption
4
- from notionary.page.content.renderer.renderers.file_like_block import FileLikeBlockRenderer
5
- from notionary.page.content.syntax import SyntaxDefinition
3
+ from notionary.blocks.schemas import (
4
+ Block,
5
+ BlockType,
6
+ ExternalFileWithCaption,
7
+ NotionHostedFileWithCaption,
8
+ )
9
+ from notionary.page.content.renderer.renderers.file_like_block import (
10
+ FileLikeBlockRenderer,
11
+ )
12
+ from notionary.page.content.syntax.definition import EnclosedSyntaxDefinition
6
13
 
7
14
 
8
15
  class PdfRenderer(FileLikeBlockRenderer):
@@ -11,9 +18,11 @@ class PdfRenderer(FileLikeBlockRenderer):
11
18
  return block.type == BlockType.PDF
12
19
 
13
20
  @override
14
- def _get_syntax(self) -> SyntaxDefinition:
21
+ def _get_syntax(self) -> EnclosedSyntaxDefinition:
15
22
  return self._syntax_registry.get_pdf_syntax()
16
23
 
17
24
  @override
18
- def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
25
+ def _get_file_data(
26
+ self, block: Block
27
+ ) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
28
  return block.pdf
@@ -1,20 +1,24 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
4
+ RichTextToMarkdownConverter,
5
+ )
4
6
  from notionary.blocks.schemas import Block, BlockType
5
7
  from notionary.page.content.renderer.context import MarkdownRenderingContext
6
8
  from notionary.page.content.renderer.renderers.base import BlockRenderer
7
- from notionary.page.content.syntax import SyntaxRegistry
9
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
8
10
 
9
11
 
10
12
  class QuoteRenderer(BlockRenderer):
11
13
  def __init__(
12
14
  self,
13
- syntax_registry: SyntaxRegistry | None = None,
15
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
14
16
  rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
15
17
  ) -> None:
16
18
  super().__init__(syntax_registry=syntax_registry)
17
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
19
+ self._rich_text_markdown_converter = (
20
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
21
+ )
18
22
 
19
23
  @override
20
24
  def _can_handle(self, block: Block) -> bool:
@@ -30,7 +34,9 @@ class QuoteRenderer(BlockRenderer):
30
34
 
31
35
  syntax = self._syntax_registry.get_quote_syntax()
32
36
  quote_lines = markdown.split("\n")
33
- quote_markdown = "\n".join(f"{syntax.start_delimiter}{line}" for line in quote_lines)
37
+ quote_markdown = "\n".join(
38
+ f"{syntax.start_delimiter}{line}" for line in quote_lines
39
+ )
34
40
 
35
41
  if context.indent_level > 0:
36
42
  quote_markdown = context.indent_text(quote_markdown)
@@ -46,4 +52,6 @@ class QuoteRenderer(BlockRenderer):
46
52
  if not block.quote or not block.quote.rich_text:
47
53
  return None
48
54
 
49
- return await self._rich_text_markdown_converter.to_markdown(block.quote.rich_text)
55
+ return await self._rich_text_markdown_converter.to_markdown(
56
+ block.quote.rich_text
57
+ )
@@ -1,6 +1,8 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
4
+ RichTextToMarkdownConverter,
5
+ )
4
6
  from notionary.blocks.schemas import Block, BlockType
5
7
  from notionary.page.content.renderer.context import MarkdownRenderingContext
6
8
  from notionary.page.content.renderer.renderers.base import BlockRenderer
@@ -9,9 +11,13 @@ from notionary.page.content.renderer.renderers.base import BlockRenderer
9
11
  class TableRenderer(BlockRenderer):
10
12
  MINIMUM_COLUMN_WIDTH = 3
11
13
 
12
- def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
14
+ def __init__(
15
+ self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None
16
+ ) -> None:
13
17
  super().__init__()
14
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
18
+ self._rich_text_markdown_converter = (
19
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
20
+ )
15
21
  self._table_syntax = self._syntax_registry.get_table_syntax()
16
22
 
17
23
  @override
@@ -71,10 +77,14 @@ class TableRenderer(BlockRenderer):
71
77
 
72
78
  return "\n".join(markdown_lines)
73
79
 
74
- def _normalize_row_lengths(self, rows: list[list[str]], target_length: int) -> list[list[str]]:
80
+ def _normalize_row_lengths(
81
+ self, rows: list[list[str]], target_length: int
82
+ ) -> list[list[str]]:
75
83
  return [row + [""] * (target_length - len(row)) for row in rows]
76
84
 
77
- def _calculate_column_widths(self, rows: list[list[str]], num_columns: int) -> list[int]:
85
+ def _calculate_column_widths(
86
+ self, rows: list[list[str]], num_columns: int
87
+ ) -> list[int]:
78
88
  widths = [max(len(row[i]) for row in rows) for i in range(num_columns)]
79
89
  return [max(width, self.MINIMUM_COLUMN_WIDTH) for width in widths]
80
90
 
@@ -1,21 +1,25 @@
1
1
  from typing import override
2
2
 
3
3
  from notionary.blocks.enums import BlockType
4
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
5
+ RichTextToMarkdownConverter,
6
+ )
5
7
  from notionary.blocks.schemas import Block
6
8
  from notionary.page.content.renderer.context import MarkdownRenderingContext
7
9
  from notionary.page.content.renderer.renderers.base import BlockRenderer
8
- from notionary.page.content.syntax import SyntaxRegistry
10
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
9
11
 
10
12
 
11
13
  class TodoRenderer(BlockRenderer):
12
14
  def __init__(
13
15
  self,
14
- syntax_registry: SyntaxRegistry | None = None,
16
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
15
17
  rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
16
18
  ) -> None:
17
19
  super().__init__(syntax_registry=syntax_registry)
18
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
20
+ self._rich_text_markdown_converter = (
21
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
22
+ )
19
23
 
20
24
  @override
21
25
  def _can_handle(self, block: Block) -> bool:
@@ -29,7 +33,11 @@ class TodoRenderer(BlockRenderer):
29
33
  context.markdown_result = ""
30
34
  return
31
35
 
32
- syntax = self._syntax_registry.get_todo_done_syntax() if is_checked else self._syntax_registry.get_todo_syntax()
36
+ syntax = (
37
+ self._syntax_registry.get_todo_done_syntax()
38
+ if is_checked
39
+ else self._syntax_registry.get_todo_syntax()
40
+ )
33
41
 
34
42
  todo_markdown = f"{syntax.start_delimiter} {content}"
35
43
 
@@ -51,6 +59,8 @@ class TodoRenderer(BlockRenderer):
51
59
 
52
60
  content = ""
53
61
  if block.to_do.rich_text:
54
- content = await self._rich_text_markdown_converter.to_markdown(block.to_do.rich_text)
62
+ content = await self._rich_text_markdown_converter.to_markdown(
63
+ block.to_do.rich_text
64
+ )
55
65
 
56
66
  return is_checked, content
@@ -1,21 +1,25 @@
1
1
  from typing import override
2
2
 
3
3
  from notionary.blocks.enums import BlockType
4
- from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
5
+ RichTextToMarkdownConverter,
6
+ )
5
7
  from notionary.blocks.schemas import Block
6
8
  from notionary.page.content.renderer.context import MarkdownRenderingContext
7
9
  from notionary.page.content.renderer.renderers.base import BlockRenderer
8
- from notionary.page.content.syntax import SyntaxRegistry
10
+ from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
9
11
 
10
12
 
11
13
  class ToggleRenderer(BlockRenderer):
12
14
  def __init__(
13
15
  self,
14
- syntax_registry: SyntaxRegistry | None = None,
16
+ syntax_registry: SyntaxDefinitionRegistry | None = None,
15
17
  rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
16
18
  ) -> None:
17
19
  super().__init__(syntax_registry=syntax_registry)
18
- self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
20
+ self._rich_text_markdown_converter = (
21
+ rich_text_markdown_converter or RichTextToMarkdownConverter()
22
+ )
19
23
 
20
24
  @override
21
25
  def _can_handle(self, block: Block) -> bool:
@@ -1,8 +1,15 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType, ExternalFileWithCaption, NotionHostedFileWithCaption
4
- from notionary.page.content.renderer.renderers.file_like_block import FileLikeBlockRenderer
5
- from notionary.page.content.syntax import SyntaxDefinition
3
+ from notionary.blocks.schemas import (
4
+ Block,
5
+ BlockType,
6
+ ExternalFileWithCaption,
7
+ NotionHostedFileWithCaption,
8
+ )
9
+ from notionary.page.content.renderer.renderers.file_like_block import (
10
+ FileLikeBlockRenderer,
11
+ )
12
+ from notionary.page.content.syntax.definition import EnclosedSyntaxDefinition
6
13
 
7
14
 
8
15
  class VideoRenderer(FileLikeBlockRenderer):
@@ -11,9 +18,11 @@ class VideoRenderer(FileLikeBlockRenderer):
11
18
  return block.type == BlockType.VIDEO
12
19
 
13
20
  @override
14
- def _get_syntax(self) -> SyntaxDefinition:
21
+ def _get_syntax(self) -> EnclosedSyntaxDefinition:
15
22
  return self._syntax_registry.get_video_syntax()
16
23
 
17
24
  @override
18
- def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
25
+ def _get_file_data(
26
+ self, block: Block
27
+ ) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
28
  return block.video
@@ -1,6 +1,8 @@
1
1
  from notionary.blocks.schemas import Block
2
2
  from notionary.page.content.renderer.context import MarkdownRenderingContext
3
- from notionary.page.content.renderer.post_processing.service import MarkdownRenderingPostProcessor
3
+ from notionary.page.content.renderer.post_processing.service import (
4
+ MarkdownRenderingPostProcessor,
5
+ )
4
6
  from notionary.page.content.renderer.renderers import BlockRenderer
5
7
  from notionary.utils.mixins.logging import LoggingMixin
6
8
 
@@ -22,7 +24,9 @@ class NotionToMarkdownConverter(LoggingMixin):
22
24
  current_block_index = 0
23
25
 
24
26
  while current_block_index < len(blocks):
25
- context = self._create_rendering_context(blocks, current_block_index, indent_level)
27
+ context = self._create_rendering_context(
28
+ blocks, current_block_index, indent_level
29
+ )
26
30
  await self._renderer_chain.handle(context)
27
31
 
28
32
  if context.markdown_result:
@@ -45,6 +49,8 @@ class NotionToMarkdownConverter(LoggingMixin):
45
49
  convert_children_callback=self.convert,
46
50
  )
47
51
 
48
- def _join_rendered_blocks(self, rendered_parts: list[str], indent_level: int) -> str:
52
+ def _join_rendered_blocks(
53
+ self, rendered_parts: list[str], indent_level: int
54
+ ) -> str:
49
55
  separator = "\n\n" if indent_level == 0 else "\n"
50
56
  return separator.join(rendered_parts)
@@ -30,13 +30,17 @@ class PageContentService(LoggingMixin):
30
30
 
31
31
  @time_execution_async()
32
32
  async def clear(self) -> None:
33
- children_response = await self._block_client.get_block_children(block_id=self._page_id)
33
+ children_response = await self._block_client.get_block_children(
34
+ block_id=self._page_id
35
+ )
34
36
 
35
37
  if not children_response or not children_response.results:
36
38
  self.logger.debug("No blocks to delete for page: %s", self._page_id)
37
39
  return
38
40
 
39
- await asyncio.gather(*[self._delete_single_block(block) for block in children_response.results])
41
+ await asyncio.gather(
42
+ *[self._delete_single_block(block) for block in children_response.results]
43
+ )
40
44
 
41
45
  @async_retry(max_retries=10, initial_delay=0.2, backoff_factor=1.5)
42
46
  async def _delete_single_block(self, block: Block) -> None:
@@ -44,16 +48,22 @@ class PageContentService(LoggingMixin):
44
48
  await self._block_client.delete_block(block.id)
45
49
 
46
50
  @time_execution_async()
47
- async def append_markdown(self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]) -> None:
51
+ async def append_markdown(
52
+ self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]
53
+ ) -> None:
48
54
  markdown = self._extract_markdown(content)
49
55
  if not markdown:
50
- self.logger.debug("No markdown content to append for page: %s", self._page_id)
56
+ self.logger.debug(
57
+ "No markdown content to append for page: %s", self._page_id
58
+ )
51
59
  return
52
60
 
53
61
  blocks = await self._markdown_converter.convert(markdown)
54
62
  await self._append_blocks(blocks)
55
63
 
56
- def _extract_markdown(self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]) -> str:
64
+ def _extract_markdown(
65
+ self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]
66
+ ) -> str:
57
67
  if isinstance(content, str):
58
68
  return content
59
69
 
@@ -62,7 +72,11 @@ class PageContentService(LoggingMixin):
62
72
  content(builder)
63
73
  return builder.build()
64
74
 
65
- raise ValueError("content must be either a string or a callable that takes a MarkdownBuilder")
75
+ raise ValueError(
76
+ "content must be either a string or a callable that takes a MarkdownBuilder"
77
+ )
66
78
 
67
79
  async def _append_blocks(self, blocks: list[Block]) -> None:
68
- await self._block_client.append_block_children(block_id=self._page_id, children=blocks)
80
+ await self._block_client.append_block_children(
81
+ block_id=self._page_id, children=blocks
82
+ )
@@ -0,0 +1,11 @@
1
+ from .grammar import MarkdownGrammar
2
+ from .models import EnclosedSyntaxDefinition, SimpleSyntaxDefinition, SyntaxDefinition
3
+ from .registry import SyntaxDefinitionRegistry
4
+
5
+ __all__ = [
6
+ "EnclosedSyntaxDefinition",
7
+ "MarkdownGrammar",
8
+ "SimpleSyntaxDefinition",
9
+ "SyntaxDefinition",
10
+ "SyntaxDefinitionRegistry",
11
+ ]
@@ -0,0 +1,57 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from enum import StrEnum
4
+
5
+
6
+ class SyntaxDefinitionRegistryKey(StrEnum):
7
+ AUDIO = "audio"
8
+ BOOKMARK = "bookmark"
9
+ IMAGE = "image"
10
+ VIDEO = "video"
11
+ FILE = "file"
12
+ PDF = "pdf"
13
+
14
+ BULLETED_LIST = "bulleted_list"
15
+ NUMBERED_LIST = "numbered_list"
16
+ TO_DO = "todo"
17
+ TO_DO_DONE = "todo_done"
18
+
19
+ TOGGLE = "toggle"
20
+ TOGGLEABLE_HEADING = "toggleable_heading"
21
+ CALLOUT = "callout"
22
+ QUOTE = "quote"
23
+ CODE = "code"
24
+
25
+ COLUMN_LIST = "column_list"
26
+ COLUMN = "column"
27
+
28
+ HEADING = "heading"
29
+
30
+ DIVIDER = "divider"
31
+ BREADCRUMB = "breadcrumb"
32
+ TABLE_OF_CONTENTS = "table_of_contents"
33
+ EQUATION = "equation"
34
+ EMBED = "embed"
35
+ TABLE = "table"
36
+ TABLE_ROW = "table_row"
37
+
38
+ CAPTION = "caption"
39
+ SPACE = "space"
40
+ PARAGRAPH = "paragraph"
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class SimpleSyntaxDefinition:
45
+ start_delimiter: str
46
+ regex_pattern: re.Pattern
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class EnclosedSyntaxDefinition:
51
+ start_delimiter: str
52
+ end_delimiter: str
53
+ regex_pattern: re.Pattern
54
+ end_regex_pattern: re.Pattern
55
+
56
+
57
+ type SyntaxDefinition = SimpleSyntaxDefinition | EnclosedSyntaxDefinition