notionary 0.2.19__py3-none-any.whl → 0.2.21__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 (205) 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 +263 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +42 -104
  7. notionary/blocks/audio/audio_markdown_node.py +3 -1
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +30 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +46 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
  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 +40 -55
  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 +40 -89
  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 +7 -0
  27. notionary/blocks/child_database/child_database_models.py +19 -0
  28. notionary/blocks/child_page/__init__.py +9 -0
  29. notionary/blocks/child_page/child_page_models.py +12 -0
  30. notionary/blocks/{shared/block_client.py → client.py} +55 -54
  31. notionary/blocks/code/__init__.py +6 -2
  32. notionary/blocks/code/code_element.py +53 -187
  33. notionary/blocks/code/code_markdown_node.py +13 -13
  34. notionary/blocks/code/code_models.py +94 -0
  35. notionary/blocks/column/__init__.py +25 -1
  36. notionary/blocks/column/column_element.py +40 -314
  37. notionary/blocks/column/column_list_element.py +37 -0
  38. notionary/blocks/column/column_list_markdown_node.py +50 -0
  39. notionary/blocks/column/column_markdown_node.py +59 -0
  40. notionary/blocks/column/column_models.py +26 -0
  41. notionary/blocks/divider/__init__.py +9 -2
  42. notionary/blocks/divider/divider_element.py +26 -49
  43. notionary/blocks/divider/divider_markdown_node.py +2 -1
  44. notionary/blocks/divider/divider_models.py +12 -0
  45. notionary/blocks/embed/__init__.py +9 -2
  46. notionary/blocks/embed/embed_element.py +47 -114
  47. notionary/blocks/embed/embed_markdown_node.py +3 -1
  48. notionary/blocks/embed/embed_models.py +14 -0
  49. notionary/blocks/equation/__init__.py +14 -0
  50. notionary/blocks/equation/equation_element.py +80 -0
  51. notionary/blocks/equation/equation_element_markdown_node.py +36 -0
  52. notionary/blocks/equation/equation_models.py +11 -0
  53. notionary/blocks/file/__init__.py +25 -0
  54. notionary/blocks/file/file_element.py +93 -0
  55. notionary/blocks/file/file_element_markdown_node.py +35 -0
  56. notionary/blocks/file/file_element_models.py +39 -0
  57. notionary/blocks/heading/__init__.py +16 -2
  58. notionary/blocks/heading/heading_element.py +67 -72
  59. notionary/blocks/heading/heading_markdown_node.py +2 -1
  60. notionary/blocks/heading/heading_models.py +29 -0
  61. notionary/blocks/image_block/__init__.py +13 -0
  62. notionary/blocks/image_block/image_element.py +84 -0
  63. notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
  64. notionary/blocks/image_block/image_models.py +10 -0
  65. notionary/blocks/models.py +172 -0
  66. notionary/blocks/numbered_list/__init__.py +12 -2
  67. notionary/blocks/numbered_list/numbered_list_element.py +33 -58
  68. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  69. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  70. notionary/blocks/paragraph/__init__.py +12 -2
  71. notionary/blocks/paragraph/paragraph_element.py +27 -69
  72. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  73. notionary/blocks/paragraph/paragraph_models.py +16 -0
  74. notionary/blocks/pdf/__init__.py +13 -0
  75. notionary/blocks/pdf/pdf_element.py +91 -0
  76. notionary/blocks/pdf/pdf_markdown_node.py +35 -0
  77. notionary/blocks/pdf/pdf_models.py +11 -0
  78. notionary/blocks/quote/__init__.py +11 -2
  79. notionary/blocks/quote/quote_element.py +31 -65
  80. notionary/blocks/quote/quote_markdown_node.py +4 -1
  81. notionary/blocks/quote/quote_models.py +18 -0
  82. notionary/blocks/registry/__init__.py +4 -0
  83. notionary/blocks/registry/block_registry.py +75 -91
  84. notionary/blocks/registry/block_registry_builder.py +107 -59
  85. notionary/blocks/rich_text/__init__.py +33 -0
  86. notionary/blocks/rich_text/rich_text_models.py +188 -0
  87. notionary/blocks/rich_text/text_inline_formatter.py +125 -0
  88. notionary/blocks/table/__init__.py +16 -2
  89. notionary/blocks/table/table_element.py +48 -241
  90. notionary/blocks/table/table_markdown_node.py +2 -1
  91. notionary/blocks/table/table_models.py +28 -0
  92. notionary/blocks/table_of_contents/__init__.py +19 -0
  93. notionary/blocks/table_of_contents/table_of_contents_element.py +51 -0
  94. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  95. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  96. notionary/blocks/todo/__init__.py +9 -2
  97. notionary/blocks/todo/todo_element.py +38 -95
  98. notionary/blocks/todo/todo_markdown_node.py +2 -1
  99. notionary/blocks/todo/todo_models.py +19 -0
  100. notionary/blocks/toggle/__init__.py +13 -3
  101. notionary/blocks/toggle/toggle_element.py +57 -264
  102. notionary/blocks/toggle/toggle_markdown_node.py +24 -14
  103. notionary/blocks/toggle/toggle_models.py +17 -0
  104. notionary/blocks/toggleable_heading/__init__.py +6 -2
  105. notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
  106. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  107. notionary/blocks/types.py +61 -0
  108. notionary/blocks/video/__init__.py +8 -2
  109. notionary/blocks/video/video_element.py +67 -143
  110. notionary/blocks/video/video_element_models.py +10 -0
  111. notionary/blocks/video/video_markdown_node.py +3 -1
  112. notionary/database/client.py +3 -8
  113. notionary/database/database.py +13 -14
  114. notionary/database/database_filter_builder.py +2 -2
  115. notionary/database/database_provider.py +5 -4
  116. notionary/database/models.py +337 -0
  117. notionary/database/notion_database.py +6 -7
  118. notionary/file_upload/client.py +5 -7
  119. notionary/file_upload/models.py +2 -1
  120. notionary/file_upload/notion_file_upload.py +2 -3
  121. notionary/markdown/markdown_builder.py +722 -0
  122. notionary/markdown/markdown_document_model.py +228 -0
  123. notionary/{blocks → markdown}/markdown_node.py +1 -0
  124. notionary/models/notion_database_response.py +0 -338
  125. notionary/page/client.py +9 -10
  126. notionary/page/models.py +327 -0
  127. notionary/page/notion_page.py +99 -52
  128. notionary/page/notion_text_length_utils.py +119 -0
  129. notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
  130. notionary/page/reader/handler/__init__.py +17 -0
  131. notionary/page/reader/handler/base_block_renderer.py +44 -0
  132. notionary/page/reader/handler/block_processing_context.py +35 -0
  133. notionary/page/reader/handler/block_rendering_context.py +43 -0
  134. notionary/page/reader/handler/column_list_renderer.py +51 -0
  135. notionary/page/reader/handler/column_renderer.py +60 -0
  136. notionary/page/reader/handler/line_renderer.py +60 -0
  137. notionary/page/reader/handler/toggle_renderer.py +69 -0
  138. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  139. notionary/page/reader/page_content_retriever.py +69 -0
  140. notionary/page/search_filter_builder.py +2 -1
  141. notionary/page/writer/handler/__init__.py +22 -0
  142. notionary/page/writer/handler/code_handler.py +100 -0
  143. notionary/page/writer/handler/column_handler.py +141 -0
  144. notionary/page/writer/handler/column_list_handler.py +139 -0
  145. notionary/page/writer/handler/line_handler.py +35 -0
  146. notionary/page/writer/handler/line_processing_context.py +54 -0
  147. notionary/page/writer/handler/regular_line_handler.py +92 -0
  148. notionary/page/writer/handler/table_handler.py +130 -0
  149. notionary/page/writer/handler/toggle_handler.py +153 -0
  150. notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
  151. notionary/page/writer/markdown_to_notion_converter.py +76 -0
  152. notionary/telemetry/__init__.py +2 -2
  153. notionary/telemetry/service.py +4 -3
  154. notionary/user/__init__.py +2 -2
  155. notionary/user/base_notion_user.py +2 -1
  156. notionary/user/client.py +2 -3
  157. notionary/user/models.py +1 -0
  158. notionary/user/notion_bot_user.py +4 -5
  159. notionary/user/notion_user.py +3 -4
  160. notionary/user/notion_user_manager.py +3 -2
  161. notionary/user/notion_user_provider.py +1 -1
  162. notionary/util/__init__.py +3 -2
  163. notionary/util/fuzzy.py +2 -1
  164. notionary/util/logging_mixin.py +2 -2
  165. notionary/util/singleton_metaclass.py +1 -1
  166. notionary/workspace.py +3 -2
  167. {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
  168. notionary-0.2.21.dist-info/RECORD +185 -0
  169. notionary/blocks/document/__init__.py +0 -7
  170. notionary/blocks/document/document_element.py +0 -102
  171. notionary/blocks/document/document_markdown_node.py +0 -31
  172. notionary/blocks/image/__init__.py +0 -7
  173. notionary/blocks/image/image_element.py +0 -151
  174. notionary/blocks/markdown_builder.py +0 -356
  175. notionary/blocks/mention/__init__.py +0 -7
  176. notionary/blocks/mention/mention_element.py +0 -229
  177. notionary/blocks/mention/mention_markdown_node.py +0 -38
  178. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  179. notionary/blocks/prompts/element_prompt_content.py +0 -41
  180. notionary/blocks/shared/__init__.py +0 -0
  181. notionary/blocks/shared/models.py +0 -713
  182. notionary/blocks/shared/notion_block_element.py +0 -37
  183. notionary/blocks/shared/text_inline_formatter.py +0 -262
  184. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  185. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  186. notionary/database/models/page_result.py +0 -10
  187. notionary/elements/__init__.py +0 -0
  188. notionary/models/notion_block_response.py +0 -264
  189. notionary/models/notion_page_response.py +0 -78
  190. notionary/models/search_response.py +0 -0
  191. notionary/page/__init__.py +0 -0
  192. notionary/page/content/notion_text_length_utils.py +0 -87
  193. notionary/page/content/page_content_retriever.py +0 -60
  194. notionary/page/formatting/line_processor.py +0 -153
  195. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  196. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  197. notionary/page/notion_to_markdown_converter.py +0 -179
  198. notionary/page/properites/property_value_extractor.py +0 -0
  199. notionary-0.2.19.dist-info/RECORD +0 -150
  200. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  201. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  202. /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
  203. /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
  204. {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
  205. {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,16 +1,20 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Dict, Any, Optional, List
3
-
4
- from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
5
- from notionary.blocks import (
6
- NotionBlockElement,
7
- ElementPromptContent,
8
- ElementPromptBuilder,
9
- NotionBlockResult,
4
+ from typing import Optional
5
+
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.callout.callout_models import (
8
+ CalloutBlock,
9
+ CreateCalloutBlock,
10
+ EmojiIcon,
11
+ IconObject,
10
12
  )
13
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
14
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
11
15
 
12
16
 
13
- class CalloutElement(NotionBlockElement):
17
+ class CalloutElement(BaseBlockElement):
14
18
  """
15
19
  Handles conversion between Markdown callouts and Notion callout blocks.
16
20
 
@@ -23,110 +27,57 @@ class CalloutElement(NotionBlockElement):
23
27
  - emoji is an optional emoji character (enclosed in quotes)
24
28
  """
25
29
 
26
- # Regex pattern for callout syntax with optional emoji
27
30
  PATTERN = re.compile(
28
- r"^\[callout\]\(" # [callout]( prefix
29
- + r'([^"]+?)' # Text content (required)
30
- + r'(?:\s+"([^"]+)")?' # Optional emoji in quotes
31
- + r"\)$" # closing parenthesis
31
+ r"^\[callout\]\(" # prefix
32
+ r"([^\"]+?)" # content
33
+ r"(?:\s+\"([^\"]+)\")?" # optional emoji
34
+ r"\)$"
32
35
  )
33
36
 
34
- # Default values
35
37
  DEFAULT_EMOJI = "💡"
36
38
  DEFAULT_COLOR = "gray_background"
37
39
 
38
40
  @classmethod
39
- def match_markdown(cls, text: str) -> bool:
40
- """Check if text is a markdown callout."""
41
- return text.strip().startswith("[callout]") and bool(
42
- CalloutElement.PATTERN.match(text.strip())
43
- )
44
-
45
- @classmethod
46
- def match_notion(cls, block: Dict[str, Any]) -> bool:
47
- """Check if block is a Notion callout."""
48
- return block.get("type") == "callout"
41
+ def match_notion(cls, block: Block) -> bool:
42
+ return block.type == BlockType.CALLOUT and block.callout
49
43
 
50
44
  @classmethod
51
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
52
- """Convert markdown callout to Notion callout block."""
53
- callout_match = CalloutElement.PATTERN.match(text.strip())
54
- if not callout_match:
45
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
46
+ """Convert a markdown callout into a Notion CalloutBlock."""
47
+ match = cls.PATTERN.match(text.strip())
48
+ if not match:
55
49
  return None
56
50
 
57
- content = callout_match.group(1)
58
- emoji = callout_match.group(2)
59
-
51
+ content, emoji = match.group(1), match.group(2)
60
52
  if not content:
61
53
  return None
62
54
 
63
- # Use default emoji if none provided
64
55
  if not emoji:
65
- emoji = CalloutElement.DEFAULT_EMOJI
56
+ emoji = cls.DEFAULT_EMOJI
66
57
 
67
- callout_data = {
68
- "rich_text": TextInlineFormatter.parse_inline_formatting(content.strip()),
69
- "icon": {"type": "emoji", "emoji": emoji},
70
- "color": CalloutElement.DEFAULT_COLOR,
71
- }
58
+ rich_text = TextInlineFormatter.parse_inline_formatting(content.strip())
72
59
 
73
- return {"type": "callout", "callout": callout_data}
60
+ callout_content = CalloutBlock(
61
+ rich_text=rich_text,
62
+ icon=EmojiIcon(emoji=emoji),
63
+ color=cls.DEFAULT_COLOR,
64
+ )
65
+ return CreateCalloutBlock(callout=callout_content)
74
66
 
75
67
  @classmethod
76
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
77
- """Convert Notion callout block to markdown callout."""
78
- if block.get("type") != "callout":
68
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
69
+ if block.type != BlockType.CALLOUT or not block.callout:
79
70
  return None
80
71
 
81
- callout_data = block.get("callout", {})
82
- rich_text = callout_data.get("rich_text", [])
83
- icon = callout_data.get("icon", {})
72
+ data = block.callout
84
73
 
85
- content = TextInlineFormatter.extract_text_with_formatting(rich_text)
74
+ content = TextInlineFormatter.extract_text_with_formatting(data.rich_text)
86
75
  if not content:
87
76
  return None
88
77
 
89
- emoji = CalloutElement._extract_emoji(icon)
90
-
91
- if emoji and emoji != CalloutElement.DEFAULT_EMOJI:
92
- return f'[callout]({content} "{emoji}")'
78
+ icon: Optional[IconObject] = block.callout.icon
79
+ emoji_char = icon.emoji if isinstance(icon, EmojiIcon) else cls.DEFAULT_EMOJI
93
80
 
81
+ if emoji_char and emoji_char != cls.DEFAULT_EMOJI:
82
+ return f'[callout]({content} "{emoji_char}")'
94
83
  return f"[callout]({content})"
95
-
96
- @classmethod
97
- def is_multiline(cls) -> bool:
98
- """Callouts are single-line elements."""
99
- return False
100
-
101
- @classmethod
102
- def _extract_emoji(cls, icon: Dict[str, Any]) -> str:
103
- """Extract emoji from Notion icon object."""
104
- if icon and icon.get("type") == "emoji":
105
- return icon.get("emoji", "")
106
- return ""
107
-
108
- @classmethod
109
- def get_llm_prompt_content(cls) -> ElementPromptContent:
110
- """
111
- Returns structured LLM prompt metadata for the callout element.
112
- """
113
- return (
114
- ElementPromptBuilder()
115
- .with_description(
116
- "Creates a callout block to highlight important information with an icon."
117
- )
118
- .with_usage_guidelines(
119
- "Use callouts when you want to draw attention to important information, "
120
- "tips, warnings, or notes that stand out from the main content."
121
- )
122
- .with_syntax('[callout](Text content "Optional emoji")')
123
- .with_examples(
124
- [
125
- "[callout](This is a default callout with the light bulb emoji)",
126
- '[callout](This is a callout with a bell emoji "🔔")',
127
- '[callout](Warning: This is an important note "⚠️")',
128
- '[callout](Tip: Add emoji that matches your content\'s purpose "💡")',
129
- ]
130
- )
131
- .build()
132
- )
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import Optional
4
+
4
5
  from pydantic import BaseModel
5
- from notionary.blocks.markdown_node import MarkdownNode
6
+
7
+ from notionary.markdown.markdown_node import MarkdownNode
6
8
 
7
9
 
8
10
  class CalloutMarkdownBlockParams(BaseModel):
@@ -0,0 +1,33 @@
1
+ from typing import Literal, Optional, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from notionary.blocks.file.file_element_models import FileBlock
6
+ from notionary.blocks.models import Block
7
+ from notionary.blocks.rich_text.rich_text_models import RichTextObject
8
+ from notionary.blocks.types import BlockColor
9
+
10
+
11
+ class EmojiIcon(BaseModel):
12
+ type: Literal["emoji"] = "emoji"
13
+ emoji: str
14
+
15
+
16
+ class FileIcon(BaseModel):
17
+ type: Literal["file"] = "file"
18
+ file: FileBlock
19
+
20
+
21
+ IconObject = Union[EmojiIcon, FileIcon]
22
+
23
+
24
+ class CalloutBlock(BaseModel):
25
+ rich_text: list[RichTextObject]
26
+ icon: Optional[IconObject] = None
27
+ color: BlockColor = BlockColor.DEFAULT
28
+ children: list[Block] = Field(default_factory=list)
29
+
30
+
31
+ class CreateCalloutBlock(BaseModel):
32
+ type: Literal["callout"] = "callout"
33
+ callout: CalloutBlock
@@ -0,0 +1,7 @@
1
+ from notionary.blocks.child_database.child_database_models import (
2
+ CreateInlineDatabaseRequest,
3
+ )
4
+
5
+ __all__ = [
6
+ "CreateInlineDatabaseRequest",
7
+ ]
@@ -0,0 +1,19 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from notionary.blocks.rich_text.rich_text_models import RichTextObject
6
+
7
+
8
+ class CreateInlineDatabaseRequest(BaseModel):
9
+ """
10
+ Minimaler Create-Payload für eine inline Database.
11
+ Parent wird von der Page-Schicht gesetzt: {"type": "page_id", "page_id": "..."}.
12
+ """
13
+
14
+ parent: dict[str, str] # wird von außen injiziert
15
+ title: list[
16
+ RichTextObject
17
+ ] # z. B. [RichTextObject.from_plain_text("Monatsübersicht")]
18
+ properties: dict[str, dict[str, Any]] # mindestens eine Title-Property erforderlich
19
+ is_inline: bool = True # inline = erscheint als child_database-Block auf der Page
@@ -0,0 +1,9 @@
1
+ from notionary.blocks.child_page.child_page_models import (
2
+ ChildPageBlock,
3
+ CreateChildPageBlock,
4
+ )
5
+
6
+ __all__ = [
7
+ "ChildPageBlock",
8
+ "CreateChildPageBlock",
9
+ ]
@@ -0,0 +1,12 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class ChildPageBlock(BaseModel):
7
+ title: str
8
+
9
+
10
+ class CreateChildPageBlock(BaseModel):
11
+ type: Literal["child_page"] = "child_page"
12
+ child_page: ChildPageBlock
@@ -1,10 +1,9 @@
1
- from typing import Optional, Dict, Any
1
+ from typing import Any, Optional
2
+
2
3
  from notionary.base_notion_client import BaseNotionClient
3
- from notionary.util import singleton
4
- from notionary.blocks.shared.models import Block, BlockChildrenResponse
4
+ from notionary.blocks.models import Block, BlockChildrenResponse, BlockCreateRequest
5
5
 
6
6
 
7
- @singleton
8
7
  class NotionBlockClient(BaseNotionClient):
9
8
  """
10
9
  Client for Notion Block API operations.
@@ -25,6 +24,37 @@ class NotionBlockClient(BaseNotionClient):
25
24
  return None
26
25
  return None
27
26
 
27
+ # das hier ist falsch (Columns werden nicht richtig abgebildet)
28
+ async def get_blocks_by_page_id_recursively(
29
+ self, page_id: str, parent_id: Optional[str] = None
30
+ ) -> list[Block]:
31
+ response = (
32
+ await self.get_block_children(block_id=page_id)
33
+ if parent_id is None
34
+ else await self.get_block_children(block_id=parent_id)
35
+ )
36
+
37
+ if not response or not response.results:
38
+ return []
39
+
40
+ blocks = response.results
41
+
42
+ for block in blocks:
43
+ if not block.has_children:
44
+ continue
45
+
46
+ block_id = block.id
47
+ if not block_id:
48
+ continue
49
+
50
+ children = await self.get_blocks_by_page_id_recursively(
51
+ page_id=page_id, parent_id=block_id
52
+ )
53
+ if children:
54
+ block.children = children
55
+
56
+ return blocks
57
+
28
58
  async def get_block_children(
29
59
  self, block_id: str, start_cursor: Optional[str] = None, page_size: int = 100
30
60
  ) -> Optional[BlockChildrenResponse]:
@@ -38,13 +68,15 @@ class NotionBlockClient(BaseNotionClient):
38
68
  params["start_cursor"] = start_cursor
39
69
 
40
70
  response = await self.get(f"blocks/{block_id}/children", params=params)
41
- if response:
42
- try:
43
- return BlockChildrenResponse.model_validate(response)
44
- except Exception as e:
45
- self.logger.error("Failed to parse block children response: %s", str(e))
46
- return None
47
- return None
71
+
72
+ if not response:
73
+ return None
74
+
75
+ try:
76
+ return BlockChildrenResponse.model_validate(response)
77
+ except Exception as e:
78
+ self.logger.error("Failed to parse block children response: %s", str(e))
79
+ return None
48
80
 
49
81
  async def get_all_block_children(self, block_id: str) -> list[Block]:
50
82
  """
@@ -74,7 +106,10 @@ class NotionBlockClient(BaseNotionClient):
74
106
  return all_blocks
75
107
 
76
108
  async def append_block_children(
77
- self, block_id: str, children: list[Dict[str, Any]], after: Optional[str] = None
109
+ self,
110
+ block_id: str,
111
+ children: list[BlockCreateRequest],
112
+ after: Optional[str] = None,
78
113
  ) -> Optional[BlockChildrenResponse]:
79
114
  """
80
115
  Appends new child blocks to a parent block.
@@ -86,15 +121,18 @@ class NotionBlockClient(BaseNotionClient):
86
121
 
87
122
  self.logger.debug("Appending %d children to block: %s", len(children), block_id)
88
123
 
124
+ # Convert Pydantic models to dictionaries for API
125
+ children_dicts = [block.model_dump(exclude_none=True) for block in children]
126
+
89
127
  # If 100 or fewer blocks, use single request
90
- if len(children) <= 100:
91
- return await self._append_single_batch(block_id, children, after)
128
+ if len(children_dicts) <= 100:
129
+ return await self._append_single_batch(block_id, children_dicts, after)
92
130
 
93
131
  # For more than 100 blocks, use batch processing
94
- return await self._append_multiple_batches(block_id, children, after)
132
+ return await self._append_multiple_batches(block_id, children_dicts, after)
95
133
 
96
134
  async def _append_single_batch(
97
- self, block_id: str, children: list[Dict[str, Any]], after: Optional[str] = None
135
+ self, block_id: str, children: list[dict[str, Any]], after: Optional[str] = None
98
136
  ) -> Optional[BlockChildrenResponse]:
99
137
  """
100
138
  Appends a single batch of blocks (≤100).
@@ -113,7 +151,7 @@ class NotionBlockClient(BaseNotionClient):
113
151
  return None
114
152
 
115
153
  async def _append_multiple_batches(
116
- self, block_id: str, children: list[Dict[str, Any]], after: Optional[str] = None
154
+ self, block_id: str, children: list[dict[str, Any]], after: Optional[str] = None
117
155
  ) -> Optional[BlockChildrenResponse]:
118
156
  """
119
157
  Appends multiple batches of blocks, handling pagination.
@@ -206,27 +244,6 @@ class NotionBlockClient(BaseNotionClient):
206
244
  request_id=responses[-1].request_id, # Use last request ID
207
245
  )
208
246
 
209
- async def update_block(
210
- self, block_id: str, block_data: Dict[str, Any], archived: Optional[bool] = None
211
- ) -> Optional[Block]:
212
- """
213
- Updates an existing block.
214
- """
215
- self.logger.debug("Updating block: %s", block_id)
216
-
217
- data = block_data.copy()
218
- if archived is not None:
219
- data["archived"] = archived
220
-
221
- response = await self.patch(f"blocks/{block_id}", data)
222
- if response:
223
- try:
224
- return Block.model_validate(response)
225
- except Exception as e:
226
- self.logger.error("Failed to parse update response: %s", str(e))
227
- return None
228
- return None
229
-
230
247
  async def delete_block(self, block_id: str) -> Optional[Block]:
231
248
  """
232
249
  Deletes (archives) a block.
@@ -238,19 +255,3 @@ class NotionBlockClient(BaseNotionClient):
238
255
  # After deletion, retrieve the block to return the updated state
239
256
  return await self.get_block(block_id)
240
257
  return None
241
-
242
- async def archive_block(self, block_id: str) -> Optional[Block]:
243
- """
244
- Archives a block by setting archived=True.
245
- """
246
- self.logger.debug("Archiving block: %s", block_id)
247
-
248
- return await self.update_block(block_id=block_id, block_data={}, archived=True)
249
-
250
- async def unarchive_block(self, block_id: str) -> Optional[Block]:
251
- """
252
- Unarchives a block by setting archived=False.
253
- """
254
- self.logger.debug("Unarchiving block: %s", block_id)
255
-
256
- return await self.update_block(block_id=block_id, block_data={}, archived=False)
@@ -1,7 +1,11 @@
1
- from .code_element import CodeElement
2
- from .code_markdown_node import CodeMarkdownNode
1
+ from notionary.blocks.code.code_element import CodeElement
2
+ from notionary.blocks.code.code_markdown_node import CodeMarkdownNode
3
+ from notionary.blocks.code.code_models import CodeBlock, CodeLanguage, CreateCodeBlock
3
4
 
4
5
  __all__ = [
5
6
  "CodeElement",
7
+ "CodeBlock",
8
+ "CodeLanguage",
9
+ "CreateCodeBlock",
6
10
  "CodeMarkdownNode",
7
11
  ]