notionary 0.2.23__py3-none-any.whl → 0.2.25__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 (101) hide show
  1. notionary/__init__.py +1 -1
  2. notionary/blocks/__init__.py +3 -1
  3. notionary/blocks/audio/__init__.py +0 -2
  4. notionary/blocks/audio/audio_element.py +92 -49
  5. notionary/blocks/audio/audio_markdown_node.py +4 -17
  6. notionary/blocks/bookmark/__init__.py +0 -2
  7. notionary/blocks/bookmark/bookmark_markdown_node.py +5 -21
  8. notionary/blocks/breadcrumbs/__init__.py +0 -2
  9. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +2 -21
  10. notionary/blocks/bulleted_list/__init__.py +0 -2
  11. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +3 -17
  12. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -1
  13. notionary/blocks/callout/__init__.py +0 -2
  14. notionary/blocks/callout/callout_markdown_node.py +4 -18
  15. notionary/blocks/callout/callout_models.py +3 -4
  16. notionary/blocks/code/code_markdown_node.py +5 -19
  17. notionary/blocks/column/__init__.py +0 -4
  18. notionary/blocks/column/column_list_markdown_node.py +3 -19
  19. notionary/blocks/column/column_markdown_node.py +4 -21
  20. notionary/blocks/divider/__init__.py +0 -2
  21. notionary/blocks/divider/divider_markdown_node.py +2 -16
  22. notionary/blocks/embed/__init__.py +0 -2
  23. notionary/blocks/embed/embed_markdown_node.py +4 -17
  24. notionary/blocks/equation/__init__.py +0 -1
  25. notionary/blocks/equation/equation_element_markdown_node.py +3 -15
  26. notionary/blocks/file/__init__.py +0 -2
  27. notionary/blocks/file/file_element.py +67 -46
  28. notionary/blocks/file/file_element_markdown_node.py +4 -17
  29. notionary/blocks/heading/__init__.py +0 -2
  30. notionary/blocks/heading/heading_markdown_node.py +5 -19
  31. notionary/blocks/heading/heading_models.py +3 -3
  32. notionary/blocks/image_block/__init__.py +0 -2
  33. notionary/blocks/image_block/image_element.py +66 -25
  34. notionary/blocks/image_block/image_markdown_node.py +5 -20
  35. notionary/{markdown → blocks/markdown}/markdown_builder.py +29 -233
  36. notionary/blocks/markdown/markdown_node.py +25 -0
  37. notionary/blocks/mixins/file_upload/__init__.py +3 -0
  38. notionary/blocks/mixins/file_upload/file_upload_mixin.py +320 -0
  39. notionary/blocks/numbered_list/__init__.py +0 -1
  40. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -17
  41. notionary/blocks/numbered_list/numbered_list_models.py +3 -3
  42. notionary/blocks/paragraph/__init__.py +0 -2
  43. notionary/blocks/paragraph/paragraph_markdown_node.py +3 -13
  44. notionary/blocks/pdf/__init__.py +0 -2
  45. notionary/blocks/pdf/pdf_element.py +81 -32
  46. notionary/blocks/pdf/pdf_markdown_node.py +5 -18
  47. notionary/blocks/quote/__init__.py +0 -2
  48. notionary/blocks/quote/quote_markdown_node.py +3 -13
  49. notionary/blocks/registry/__init__.py +1 -2
  50. notionary/blocks/registry/block_registry.py +116 -61
  51. notionary/blocks/table/__init__.py +0 -2
  52. notionary/blocks/table/table_markdown_node.py +17 -16
  53. notionary/blocks/table_of_contents/__init__.py +0 -2
  54. notionary/blocks/table_of_contents/table_of_contents_element.py +27 -15
  55. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +3 -17
  56. notionary/blocks/table_of_contents/table_of_contents_models.py +2 -2
  57. notionary/blocks/todo/__init__.py +0 -2
  58. notionary/blocks/todo/todo_markdown_node.py +9 -20
  59. notionary/blocks/todo/todo_models.py +2 -3
  60. notionary/blocks/toggle/__init__.py +0 -2
  61. notionary/blocks/toggle/toggle_markdown_node.py +5 -19
  62. notionary/blocks/toggleable_heading/__init__.py +0 -2
  63. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +6 -23
  64. notionary/blocks/video/__init__.py +0 -2
  65. notionary/blocks/video/video_element.py +110 -34
  66. notionary/blocks/video/video_markdown_node.py +4 -15
  67. notionary/comments/client.py +1 -1
  68. notionary/file_upload/client.py +3 -2
  69. notionary/file_upload/models.py +10 -1
  70. notionary/file_upload/notion_file_upload.py +5 -5
  71. notionary/page/markdown_whitespace_processor.py +129 -0
  72. notionary/page/notion_page.py +35 -40
  73. notionary/page/page_content_deleting_service.py +1 -1
  74. notionary/page/page_content_writer.py +32 -129
  75. notionary/page/page_context.py +0 -5
  76. notionary/page/reader/handler/column_list_renderer.py +2 -2
  77. notionary/page/reader/handler/column_renderer.py +2 -2
  78. notionary/page/reader/handler/line_renderer.py +2 -2
  79. notionary/page/reader/handler/toggle_renderer.py +2 -2
  80. notionary/page/reader/handler/toggleable_heading_renderer.py +2 -2
  81. notionary/page/writer/handler/equation_handler.py +1 -1
  82. notionary/page/writer/handler/toggle_handler.py +8 -4
  83. notionary/page/writer/handler/toggleable_heading_handler.py +3 -2
  84. notionary/page/writer/markdown_to_notion_converter.py +74 -30
  85. notionary/schemas/__init__.py +3 -0
  86. notionary/schemas/base.py +73 -0
  87. notionary/shared/__init__.py +1 -3
  88. notionary-0.2.25.dist-info/METADATA +270 -0
  89. {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/RECORD +92 -94
  90. notionary/blocks/guards.py +0 -22
  91. notionary/blocks/registry/block_registry_builder.py +0 -264
  92. notionary/markdown/makdown_document_model.py +0 -0
  93. notionary/markdown/markdown_document_model.py +0 -228
  94. notionary/markdown/markdown_node.py +0 -30
  95. notionary/models/notion_database_response.py +0 -0
  96. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -73
  97. notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
  98. notionary-0.2.23.dist-info/METADATA +0 -235
  99. /notionary/{markdown/___init__.py → blocks/markdown/markdown_document_model.py} +0 -0
  100. {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/LICENSE +0 -0
  101. {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/WHEEL +0 -0
notionary/__init__.py CHANGED
@@ -4,7 +4,7 @@ bootstrap_blocks()
4
4
 
5
5
  from .database import DatabaseFilterBuilder, NotionDatabase
6
6
  from .file_upload import NotionFileUpload
7
- from .markdown.markdown_builder import MarkdownBuilder
7
+ from .blocks.markdown.markdown_builder import MarkdownBuilder
8
8
  from .page.notion_page import NotionPage
9
9
  from .user import NotionBotUser, NotionUser, NotionUserManager
10
10
  from .workspace import NotionWorkspace
@@ -1,3 +1,5 @@
1
1
  from ._bootstrap import bootstrap_blocks
2
2
 
3
- __all__ = ["bootstrap_blocks"]
3
+ __all__ = [
4
+ "bootstrap_blocks",
5
+ ]
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.audio.audio_element import AudioElement
2
2
  from notionary.blocks.audio.audio_markdown_node import (
3
- AudioMarkdownBlockParams,
4
3
  AudioMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.audio.audio_models import CreateAudioBlock
@@ -9,5 +8,4 @@ __all__ = [
9
8
  "AudioElement",
10
9
  "CreateAudioBlock",
11
10
  "AudioMarkdownNode",
12
- "AudioMarkdownBlockParams",
13
11
  ]
@@ -1,46 +1,38 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ from pathlib import Path
4
5
  from typing import Optional
5
6
 
6
7
  from notionary.blocks.audio.audio_models import CreateAudioBlock
7
8
  from notionary.blocks.base_block_element import BaseBlockElement
8
- from notionary.blocks.file.file_element_models import ExternalFile, FileBlock, FileType
9
+ from notionary.blocks.file.file_element_models import (
10
+ ExternalFile,
11
+ FileBlock,
12
+ FileType,
13
+ FileUploadFile,
14
+ )
9
15
  from notionary.blocks.mixins.captions import CaptionMixin
16
+ from notionary.blocks.mixins.file_upload.file_upload_mixin import FileUploadMixin
10
17
  from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
11
18
  from notionary.blocks.models import Block, BlockCreateResult, BlockType
19
+ from notionary.util.logging_mixin import LoggingMixin
12
20
 
13
21
 
14
- class AudioElement(BaseBlockElement, CaptionMixin):
15
- """
22
+ class AudioElement(BaseBlockElement, FileUploadMixin, LoggingMixin, CaptionMixin):
23
+ r"""
16
24
  Handles conversion between Markdown audio embeds and Notion audio blocks.
17
25
 
18
- Markdown audio syntax:
19
- - [audio](https://example.com/audio.mp3) - Simple audio embed
20
- - [audio](https://example.com/audio.mp3)(caption:Episode 1) - Audio with caption
21
- - (caption:Background music)[audio](https://example.com/song.mp3) - caption before URL
26
+ Supports both external URLs and local audio file uploads.
22
27
 
23
- Where:
24
- - URL is the required audio file URL
25
- - Caption supports rich text formatting and is optional
28
+ Markdown audio syntax:
29
+ - [audio](https://example.com/audio.mp3) - External URL
30
+ - [audio](./local/song.mp3) - Local audio file (will be uploaded)
31
+ - [audio](C:\Music\podcast.wav) - Absolute local path (will be uploaded)
32
+ - [audio](https://example.com/audio.mp3)(caption:Episode 1) - URL with caption
26
33
  """
27
34
 
28
- # Simple pattern that matches just the audio link, CaptionMixin handles caption separately
29
- AUDIO_PATTERN = re.compile(r"\[audio\]\((https?://[^\s\"]+)\)")
30
-
31
- @classmethod
32
- def _extract_audio_url(cls, text: str) -> Optional[str]:
33
- """Extract audio URL from text, handling caption patterns."""
34
- # First remove any captions to get clean text for URL extraction
35
- clean_text = cls.remove_caption(text)
36
-
37
- # Now extract the URL from clean text
38
- match = cls.AUDIO_PATTERN.search(clean_text)
39
- if match:
40
- return match.group(1)
41
-
42
- return None
43
-
35
+ AUDIO_PATTERN = re.compile(r"\[audio\]\(([^)]+)\)")
44
36
  SUPPORTED_EXTENSIONS = {".mp3", ".wav", ".ogg", ".oga", ".m4a"}
45
37
 
46
38
  @classmethod
@@ -49,27 +41,62 @@ class AudioElement(BaseBlockElement, CaptionMixin):
49
41
  return block.type == BlockType.AUDIO
50
42
 
51
43
  @classmethod
52
- async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
44
+ async def markdown_to_notion(cls, text: str) -> Optional[BlockCreateResult]:
53
45
  """Convert markdown audio embed to Notion audio block."""
54
- # Use our helper method to extract the URL
55
- url = cls._extract_audio_url(text.strip())
56
- if not url:
46
+ # Extract the path/URL
47
+ path = cls._extract_audio_path(text.strip())
48
+ if not path:
57
49
  return None
58
50
 
59
- if not cls._is_likely_audio_url(url):
60
- return None
51
+ # Check if it's a local file path
52
+ if cls._is_local_file_path(path):
53
+ # Verify file exists and has supported extension
54
+ audio_path = Path(path)
55
+ if not audio_path.exists():
56
+ cls.logger.warning(f"Audio file not found: {path}")
57
+ return None
61
58
 
62
- # Use mixin to extract caption (if present anywhere in text)
63
- caption_text = cls.extract_caption(text.strip())
64
- caption_rich_text = cls.build_caption_rich_text(caption_text or "")
59
+ if audio_path.suffix.lower() not in cls.SUPPORTED_EXTENSIONS:
60
+ cls.logger.warning(f"Unsupported audio format: {audio_path.suffix}")
61
+ return None
65
62
 
66
- audio_content = FileBlock(
67
- type=FileType.EXTERNAL,
68
- external=ExternalFile(url=url),
69
- caption=caption_rich_text,
70
- )
63
+ cls.logger.info(f"Uploading local audio file: {path}")
64
+
65
+ # Upload the local audio file
66
+ file_upload_id = await cls._upload_local_file(path, "audio")
67
+ if not file_upload_id:
68
+ cls.logger.error(f"Failed to upload audio file: {path}")
69
+ return None
71
70
 
72
- return CreateAudioBlock(audio=audio_content)
71
+ cls.logger.info(
72
+ f"Successfully uploaded audio file with ID: {file_upload_id}"
73
+ )
74
+
75
+ # Use mixin to extract caption (if present anywhere in text)
76
+ caption_text = cls.extract_caption(text.strip())
77
+ caption_rich_text = cls.build_caption_rich_text(caption_text or "")
78
+
79
+ audio_content = FileBlock(
80
+ type=FileType.FILE_UPLOAD,
81
+ file_upload=FileUploadFile(id=file_upload_id),
82
+ caption=caption_rich_text,
83
+ )
84
+
85
+ return CreateAudioBlock(audio=audio_content)
86
+
87
+ else:
88
+ # Handle external URL - accept any URL (validation happens at API level)
89
+ # Use mixin to extract caption (if present anywhere in text)
90
+ caption_text = cls.extract_caption(text.strip())
91
+ caption_rich_text = cls.build_caption_rich_text(caption_text or "")
92
+
93
+ audio_content = FileBlock(
94
+ type=FileType.EXTERNAL,
95
+ external=ExternalFile(url=path),
96
+ caption=caption_rich_text,
97
+ )
98
+
99
+ return CreateAudioBlock(audio=audio_content)
73
100
 
74
101
  @classmethod
75
102
  async def notion_to_markdown(cls, block: Block) -> Optional[str]:
@@ -78,11 +105,14 @@ class AudioElement(BaseBlockElement, CaptionMixin):
78
105
  return None
79
106
 
80
107
  audio = block.audio
108
+ url = None
109
+
110
+ # Handle both external URLs and uploaded files
111
+ if audio.type == FileType.EXTERNAL and audio.external is not None:
112
+ url = audio.external.url
113
+ elif audio.type == FileType.FILE_UPLOAD and audio.file_upload is not None:
114
+ url = audio.file_upload.url
81
115
 
82
- # Only handle external audio
83
- if audio.type != FileType.EXTERNAL or audio.external is None:
84
- return None
85
- url = audio.external.url
86
116
  if not url:
87
117
  return None
88
118
 
@@ -100,16 +130,29 @@ class AudioElement(BaseBlockElement, CaptionMixin):
100
130
  """Get system prompt information for audio blocks."""
101
131
  return BlockElementMarkdownInformation(
102
132
  block_type=cls.__name__,
103
- description="Audio blocks embed audio files from external URLs with optional captions",
133
+ description="Audio blocks embed audio files from external URLs or local files with optional captions",
104
134
  syntax_examples=[
105
135
  "[audio](https://example.com/song.mp3)",
136
+ "[audio](./local/podcast.wav)",
137
+ "[audio](C:\\Music\\interview.mp3)",
106
138
  "[audio](https://example.com/podcast.wav)(caption:Episode 1)",
107
- "(caption:Background music)[audio](https://soundcloud.com/track/123)",
108
- "[audio](https://example.com/interview.mp3)(caption:**Live** interview)",
139
+ "(caption:Background music)[audio](./song.mp3)",
140
+ "[audio](./interview.mp3)(caption:**Live** interview)",
109
141
  ],
110
- usage_guidelines="Use for embedding audio files like music, podcasts, or sound effects. Supports common audio formats (mp3, wav, ogg, m4a). Caption supports rich text formatting and is optional.",
142
+ usage_guidelines="Use for embedding audio files like music, podcasts, or sound effects. Supports both external URLs and local file uploads. Supports common audio formats (mp3, wav, ogg, m4a). Caption supports rich text formatting and is optional.",
111
143
  )
112
144
 
113
145
  @classmethod
114
146
  def _is_likely_audio_url(cls, url: str) -> bool:
115
147
  return any(url.lower().endswith(ext) for ext in cls.SUPPORTED_EXTENSIONS)
148
+
149
+ @classmethod
150
+ def _extract_audio_path(cls, text: str) -> Optional[str]:
151
+ """Extract audio path/URL from text, handling caption patterns."""
152
+ clean_text = cls.remove_caption(text)
153
+
154
+ match = cls.AUDIO_PATTERN.search(clean_text)
155
+ if match:
156
+ return match.group(1).strip()
157
+
158
+ return None
@@ -1,30 +1,17 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import Optional
4
2
 
5
- from pydantic import BaseModel
6
-
7
- from notionary.markdown.markdown_node import MarkdownNode
3
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
8
4
  from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
9
5
 
10
6
 
11
- class AudioMarkdownBlockParams(BaseModel):
12
- url: str
13
- caption: Optional[str] = None
14
-
15
-
16
7
  class AudioMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
17
8
  """
9
+ Enhanced Audio node with Pydantic integration.
18
10
  Programmatic interface for creating Notion-style audio blocks.
19
11
  """
20
12
 
21
- def __init__(self, url: str, caption: Optional[str] = None):
22
- self.url = url
23
- self.caption = caption
24
-
25
- @classmethod
26
- def from_params(cls, params: AudioMarkdownBlockParams) -> AudioMarkdownNode:
27
- return cls(url=params.url, caption=params.caption)
13
+ url: str
14
+ caption: Optional[str] = None
28
15
 
29
16
  def to_markdown(self) -> str:
30
17
  """Return the Markdown representation.
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.bookmark.bookmark_element import BookmarkElement
2
2
  from notionary.blocks.bookmark.bookmark_markdown_node import (
3
- BookmarkMarkdownBlockParams,
4
3
  BookmarkMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.bookmark.bookmark_models import BookmarkBlock, CreateBookmarkBlock
@@ -10,5 +9,4 @@ __all__ = [
10
9
  "BookmarkBlock",
11
10
  "CreateBookmarkBlock",
12
11
  "BookmarkMarkdownNode",
13
- "BookmarkMarkdownBlockParams",
14
12
  ]
@@ -1,34 +1,18 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import Optional
4
2
 
5
- from pydantic import BaseModel
6
-
7
- from notionary.markdown.markdown_node import MarkdownNode
3
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
8
4
  from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
9
5
 
10
6
 
11
- class BookmarkMarkdownBlockParams(BaseModel):
12
- url: str
13
- title: Optional[str] = None
14
- caption: Optional[str] = None
15
-
16
-
17
7
  class BookmarkMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
18
8
  """
9
+ Enhanced Bookmark node with Pydantic integration.
19
10
  Programmatic interface for creating Notion-style bookmark Markdown blocks.
20
11
  """
21
12
 
22
- def __init__(
23
- self, url: str, title: Optional[str] = None, caption: Optional[str] = None
24
- ):
25
- self.url = url
26
- self.title = title
27
- self.caption = caption
28
-
29
- @classmethod
30
- def from_params(cls, params: BookmarkMarkdownBlockParams) -> BookmarkMarkdownNode:
31
- return cls(url=params.url, title=params.title, caption=params.caption)
13
+ url: str
14
+ title: Optional[str] = None
15
+ caption: Optional[str] = None
32
16
 
33
17
  def to_markdown(self) -> str:
34
18
  """Return the Markdown representation.
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.breadcrumbs.breadcrumb_element import BreadcrumbElement
2
2
  from notionary.blocks.breadcrumbs.breadcrumb_markdown_node import (
3
- BreadcrumbMarkdownBlockParams,
4
3
  BreadcrumbMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.breadcrumbs.breadcrumb_models import (
@@ -13,5 +12,4 @@ __all__ = [
13
12
  "BreadcrumbBlock",
14
13
  "CreateBreadcrumbBlock",
15
14
  "BreadcrumbMarkdownNode",
16
- "BreadcrumbMarkdownBlockParams",
17
15
  ]
@@ -1,32 +1,13 @@
1
- from __future__ import annotations
2
-
3
- from pydantic import BaseModel
4
-
5
- from notionary.markdown.markdown_node import MarkdownNode
6
-
7
-
8
- class BreadcrumbMarkdownBlockParams(BaseModel):
9
- """Parameters for breadcrumb markdown block. No parameters needed."""
10
-
11
- pass
1
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
12
2
 
13
3
 
14
4
  class BreadcrumbMarkdownNode(MarkdownNode):
15
5
  """
6
+ Enhanced Breadcrumb node with Pydantic integration.
16
7
  Programmatic interface for creating Markdown breadcrumb blocks.
17
8
  Example:
18
9
  [breadcrumb]
19
10
  """
20
11
 
21
- def __init__(self):
22
- # No parameters needed for breadcrumb
23
- pass
24
-
25
- @classmethod
26
- def from_params(
27
- cls, params: BreadcrumbMarkdownBlockParams
28
- ) -> BreadcrumbMarkdownNode:
29
- return cls()
30
-
31
12
  def to_markdown(self) -> str:
32
13
  return "[breadcrumb]"
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.bulleted_list.bulleted_list_element import BulletedListElement
2
2
  from notionary.blocks.bulleted_list.bulleted_list_markdown_node import (
3
- BulletedListMarkdownBlockParams,
4
3
  BulletedListMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.bulleted_list.bulleted_list_models import (
@@ -13,5 +12,4 @@ __all__ = [
13
12
  "BulletedListItemBlock",
14
13
  "CreateBulletedListItemBlock",
15
14
  "BulletedListMarkdownNode",
16
- "BulletedListMarkdownBlockParams",
17
15
  ]
@@ -1,16 +1,9 @@
1
- from __future__ import annotations
2
-
3
- from pydantic import BaseModel
4
-
5
- from notionary.markdown.markdown_node import MarkdownNode
6
-
7
-
8
- class BulletedListMarkdownBlockParams(BaseModel):
9
- texts: list[str]
1
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
10
2
 
11
3
 
12
4
  class BulletedListMarkdownNode(MarkdownNode):
13
5
  """
6
+ Enhanced BulletedList node with Pydantic integration.
14
7
  Programmatic interface for creating Markdown bulleted list items.
15
8
  Example:
16
9
  - First item
@@ -18,14 +11,7 @@ class BulletedListMarkdownNode(MarkdownNode):
18
11
  - Third item
19
12
  """
20
13
 
21
- def __init__(self, texts: list[str]):
22
- self.texts = texts
23
-
24
- @classmethod
25
- def from_params(
26
- cls, params: BulletedListMarkdownBlockParams
27
- ) -> BulletedListMarkdownNode:
28
- return cls(texts=params.texts)
14
+ texts: list[str]
29
15
 
30
16
  def to_markdown(self) -> str:
31
17
  result = []
@@ -10,7 +10,6 @@ from notionary.blocks.types import BlockColor
10
10
  class BulletedListItemBlock(BaseModel):
11
11
  rich_text: list[RichTextObject]
12
12
  color: BlockColor = BlockColor.DEFAULT
13
- children: list[Block] = Field(default_factory=list)
14
13
 
15
14
 
16
15
  class CreateBulletedListItemBlock(BaseModel):
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.callout.callout_element import CalloutElement
2
2
  from notionary.blocks.callout.callout_markdown_node import (
3
- CalloutMarkdownBlockParams,
4
3
  CalloutMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.callout.callout_models import CalloutBlock, CreateCalloutBlock
@@ -10,5 +9,4 @@ __all__ = [
10
9
  "CalloutBlock",
11
10
  "CreateCalloutBlock",
12
11
  "CalloutMarkdownNode",
13
- "CalloutMarkdownBlockParams",
14
12
  ]
@@ -1,30 +1,16 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import Optional
4
-
5
- from pydantic import BaseModel
6
-
7
- from notionary.markdown.markdown_node import MarkdownNode
8
-
9
-
10
- class CalloutMarkdownBlockParams(BaseModel):
11
- text: str
12
- emoji: Optional[str] = None
2
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
13
3
 
14
4
 
15
5
  class CalloutMarkdownNode(MarkdownNode):
16
6
  """
7
+ Enhanced Callout node with Pydantic integration.
17
8
  Programmatic interface for creating Notion-style callout Markdown blocks.
18
9
  Example: [callout](This is important "⚠️")
19
10
  """
20
11
 
21
- def __init__(self, text: str, emoji: Optional[str] = None):
22
- self.text = text
23
- self.emoji = emoji
24
-
25
- @classmethod
26
- def from_params(cls, params: CalloutMarkdownBlockParams) -> CalloutMarkdownNode:
27
- return cls(text=params.text, emoji=params.emoji)
12
+ text: str
13
+ emoji: Optional[str] = None
28
14
 
29
15
  def to_markdown(self) -> str:
30
16
  if self.emoji and self.emoji != "💡":
@@ -1,6 +1,6 @@
1
1
  from typing import Literal, Optional, Union
2
2
 
3
- from pydantic import BaseModel, Field
3
+ from pydantic import BaseModel, Field, model_serializer
4
4
 
5
5
  from notionary.blocks.file.file_element_models import FileBlock
6
6
  from notionary.blocks.models import Block
@@ -23,10 +23,9 @@ IconObject = Union[EmojiIcon, FileIcon]
23
23
 
24
24
  class CalloutBlock(BaseModel):
25
25
  rich_text: list[RichTextObject]
26
- icon: Optional[IconObject] = None
27
26
  color: BlockColor = BlockColor.DEFAULT
28
- children: list[Block] = Field(default_factory=list)
29
-
27
+ icon: Optional[IconObject] = None
28
+ children: Optional[list[Block]] = None
30
29
 
31
30
  class CreateCalloutBlock(BaseModel):
32
31
  type: Literal["callout"] = "callout"
@@ -1,13 +1,11 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import Optional
4
2
 
5
- from notionary.blocks.code.code_models import CodeBlock
6
- from notionary.markdown.markdown_node import MarkdownNode
3
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
7
4
 
8
5
 
9
6
  class CodeMarkdownNode(MarkdownNode):
10
7
  """
8
+ Enhanced Code node with Pydantic integration.
11
9
  Programmatic interface for creating Notion-style Markdown code blocks.
12
10
  Automatically handles indentation normalization for multiline strings.
13
11
 
@@ -17,21 +15,9 @@ class CodeMarkdownNode(MarkdownNode):
17
15
  ```
18
16
  """
19
17
 
20
- def __init__(
21
- self,
22
- code: str,
23
- language: Optional[str] = None,
24
- caption: Optional[str] = None,
25
- ):
26
- self.code = code
27
- self.language = language or ""
28
- self.caption = caption
29
-
30
- @classmethod
31
- def from_params(cls, params: CodeBlock) -> CodeMarkdownNode:
32
- return cls(
33
- code=params.rich_text, language=params.language, caption=params.caption
34
- )
18
+ code: str
19
+ language: Optional[str] = None
20
+ caption: Optional[str] = None
35
21
 
36
22
  def to_markdown(self) -> str:
37
23
  lang = self.language or ""
@@ -1,11 +1,9 @@
1
1
  from notionary.blocks.column.column_element import ColumnElement
2
2
  from notionary.blocks.column.column_list_element import ColumnListElement
3
3
  from notionary.blocks.column.column_list_markdown_node import (
4
- ColumnListMarkdownBlockParams,
5
4
  ColumnListMarkdownNode,
6
5
  )
7
6
  from notionary.blocks.column.column_markdown_node import (
8
- ColumnMarkdownBlockParams,
9
7
  ColumnMarkdownNode,
10
8
  )
11
9
  from notionary.blocks.column.column_models import (
@@ -23,7 +21,5 @@ __all__ = [
23
21
  "ColumnListBlock",
24
22
  "CreateColumnListBlock",
25
23
  "ColumnMarkdownNode",
26
- "ColumnMarkdownBlockParams",
27
24
  "ColumnListMarkdownNode",
28
- "ColumnListMarkdownBlockParams",
29
25
  ]
@@ -1,19 +1,10 @@
1
- from __future__ import annotations
2
-
3
- from pydantic import BaseModel
4
-
5
1
  from notionary.blocks.column.column_markdown_node import ColumnMarkdownNode
6
- from notionary.markdown.markdown_document_model import MarkdownBlock
7
- from notionary.markdown.markdown_node import MarkdownNode
8
-
9
-
10
- class ColumnListMarkdownBlockParams(BaseModel):
11
- columns: list[list[MarkdownBlock]]
12
- model_config = {"arbitrary_types_allowed": True}
2
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
13
3
 
14
4
 
15
5
  class ColumnListMarkdownNode(MarkdownNode):
16
6
  """
7
+ Enhanced Column List node with Pydantic integration.
17
8
  Programmatic interface for creating a Markdown column list container.
18
9
  This represents the `::: columns` container that holds multiple columns.
19
10
 
@@ -31,14 +22,7 @@ class ColumnListMarkdownNode(MarkdownNode):
31
22
  :::
32
23
  """
33
24
 
34
- def __init__(self, columns: list[ColumnMarkdownNode]):
35
- self.columns = columns
36
-
37
- @classmethod
38
- def from_params(
39
- cls, params: ColumnListMarkdownBlockParams
40
- ) -> ColumnListMarkdownNode:
41
- return cls(columns=params.columns)
25
+ columns: list[ColumnMarkdownNode] = []
42
26
 
43
27
  def to_markdown(self) -> str:
44
28
  if not self.columns:
@@ -1,20 +1,10 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import Optional
4
-
5
- from pydantic import BaseModel
6
-
7
- from notionary.markdown.markdown_node import MarkdownNode
8
-
9
-
10
- class ColumnMarkdownBlockParams(BaseModel):
11
- children: list[MarkdownNode]
12
- width_ratio: Optional[float] = None
13
- model_config = {"arbitrary_types_allowed": True}
2
+ from notionary.blocks.markdown.markdown_node import MarkdownNode
14
3
 
15
4
 
16
5
  class ColumnMarkdownNode(MarkdownNode):
17
6
  """
7
+ Enhanced Column node with Pydantic integration.
18
8
  Programmatic interface for creating a single Markdown column block
19
9
  with nested content and optional width ratio.
20
10
 
@@ -32,15 +22,8 @@ class ColumnMarkdownNode(MarkdownNode):
32
22
  :::
33
23
  """
34
24
 
35
- def __init__(
36
- self, children: list[MarkdownNode], width_ratio: Optional[float] = None
37
- ):
38
- self.children = children
39
- self.width_ratio = width_ratio
40
-
41
- @classmethod
42
- def from_params(cls, params: ColumnMarkdownBlockParams) -> ColumnMarkdownNode:
43
- return cls(children=params.children, width_ratio=params.width_ratio)
25
+ children: list[MarkdownNode] = []
26
+ width_ratio: Optional[float] = None
44
27
 
45
28
  def to_markdown(self) -> str:
46
29
  # Start tag with optional width ratio
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.divider.divider_element import DividerElement
2
2
  from notionary.blocks.divider.divider_markdown_node import (
3
- DividerMarkdownBlockParams,
4
3
  DividerMarkdownNode,
5
4
  )
6
5
  from notionary.blocks.divider.divider_models import CreateDividerBlock, DividerBlock
@@ -10,5 +9,4 @@ __all__ = [
10
9
  "DividerBlock",
11
10
  "CreateDividerBlock",
12
11
  "DividerMarkdownNode",
13
- "DividerMarkdownBlockParams",
14
12
  ]