notionary 0.3.1__py3-none-any.whl → 0.4.0__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 (80) hide show
  1. notionary/__init__.py +6 -1
  2. notionary/blocks/enums.py +0 -6
  3. notionary/blocks/schemas.py +32 -78
  4. notionary/comments/schemas.py +2 -29
  5. notionary/data_source/properties/schemas.py +128 -107
  6. notionary/data_source/schemas.py +2 -2
  7. notionary/data_source/service.py +32 -23
  8. notionary/database/schemas.py +2 -2
  9. notionary/database/service.py +3 -5
  10. notionary/exceptions/__init__.py +6 -2
  11. notionary/exceptions/api.py +2 -2
  12. notionary/exceptions/base.py +1 -1
  13. notionary/exceptions/block_parsing.py +3 -3
  14. notionary/exceptions/data_source/builder.py +2 -2
  15. notionary/exceptions/data_source/properties.py +3 -3
  16. notionary/exceptions/file_upload.py +67 -0
  17. notionary/exceptions/properties.py +4 -4
  18. notionary/exceptions/search.py +4 -4
  19. notionary/file_upload/__init__.py +4 -0
  20. notionary/file_upload/client.py +124 -210
  21. notionary/file_upload/config/__init__.py +17 -0
  22. notionary/file_upload/config/config.py +32 -0
  23. notionary/file_upload/config/constants.py +16 -0
  24. notionary/file_upload/file/reader.py +28 -0
  25. notionary/file_upload/query/__init__.py +7 -0
  26. notionary/file_upload/query/builder.py +54 -0
  27. notionary/file_upload/query/models.py +37 -0
  28. notionary/file_upload/schemas.py +78 -0
  29. notionary/file_upload/service.py +152 -289
  30. notionary/file_upload/validation/factory.py +64 -0
  31. notionary/file_upload/validation/impl/file_name_length.py +23 -0
  32. notionary/file_upload/validation/models.py +124 -0
  33. notionary/file_upload/validation/port.py +7 -0
  34. notionary/file_upload/validation/service.py +17 -0
  35. notionary/file_upload/validation/validators/__init__.py +11 -0
  36. notionary/file_upload/validation/validators/file_exists.py +15 -0
  37. notionary/file_upload/validation/validators/file_extension.py +122 -0
  38. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  39. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  40. notionary/http/client.py +6 -22
  41. notionary/page/content/parser/factory.py +8 -5
  42. notionary/page/content/parser/parsers/audio.py +8 -33
  43. notionary/page/content/parser/parsers/embed.py +0 -2
  44. notionary/page/content/parser/parsers/file.py +8 -35
  45. notionary/page/content/parser/parsers/file_like_block.py +89 -0
  46. notionary/page/content/parser/parsers/image.py +8 -35
  47. notionary/page/content/parser/parsers/pdf.py +8 -35
  48. notionary/page/content/parser/parsers/video.py +8 -35
  49. notionary/page/content/renderer/renderers/audio.py +9 -21
  50. notionary/page/content/renderer/renderers/file.py +9 -21
  51. notionary/page/content/renderer/renderers/file_like_block.py +43 -0
  52. notionary/page/content/renderer/renderers/image.py +9 -21
  53. notionary/page/content/renderer/renderers/pdf.py +9 -21
  54. notionary/page/content/renderer/renderers/video.py +9 -21
  55. notionary/page/content/syntax/__init__.py +2 -1
  56. notionary/page/content/syntax/registry.py +38 -60
  57. notionary/page/properties/client.py +1 -1
  58. notionary/page/properties/{models.py → schemas.py} +93 -107
  59. notionary/page/properties/service.py +1 -1
  60. notionary/page/schemas.py +3 -3
  61. notionary/page/service.py +1 -1
  62. notionary/shared/entity/dto_parsers.py +1 -36
  63. notionary/shared/entity/entity_metadata_update_client.py +18 -4
  64. notionary/shared/entity/schemas.py +6 -6
  65. notionary/shared/entity/service.py +53 -30
  66. notionary/shared/models/file.py +34 -6
  67. notionary/shared/models/icon.py +5 -12
  68. notionary/user/bot.py +12 -12
  69. notionary/utils/decorators.py +8 -8
  70. notionary/workspace/__init__.py +2 -2
  71. notionary/workspace/query/__init__.py +2 -1
  72. notionary/workspace/query/service.py +3 -17
  73. notionary/workspace/service.py +45 -45
  74. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/METADATA +1 -1
  75. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/RECORD +77 -58
  76. notionary/file_upload/models.py +0 -69
  77. notionary/page/page_context.py +0 -50
  78. notionary/shared/models/cover.py +0 -20
  79. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
  80. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,42 +1,15 @@
1
- """Parser for image blocks."""
2
-
3
1
  from typing import override
4
2
 
5
- from notionary.blocks.schemas import (
6
- CreateImageBlock,
7
- ExternalFile,
8
- FileData,
9
- FileType,
10
- )
11
- from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
- from notionary.page.content.syntax import SyntaxRegistry
13
-
3
+ from notionary.blocks.schemas import CreateImageBlock, ExternalFileWithCaption
4
+ from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
5
+ from notionary.page.content.syntax import SyntaxDefinition, SyntaxRegistry
14
6
 
15
- class ImageParser(LineParser):
16
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
- super().__init__(syntax_registry)
18
- self._syntax = syntax_registry.get_image_syntax()
19
7
 
8
+ class ImageParser(FileLikeBlockParser[CreateImageBlock]):
20
9
  @override
21
- def _can_handle(self, context: BlockParsingContext) -> bool:
22
- if context.is_inside_parent_context():
23
- return False
24
- return self._syntax.regex_pattern.search(context.line) is not None
10
+ def _get_syntax(self, syntax_registry: SyntaxRegistry) -> SyntaxDefinition:
11
+ return syntax_registry.get_image_syntax()
25
12
 
26
13
  @override
27
- async def _process(self, context: BlockParsingContext) -> None:
28
- url = self._extract_url(context.line)
29
- if not url:
30
- return
31
-
32
- image_data = FileData(
33
- type=FileType.EXTERNAL,
34
- external=ExternalFile(url=url),
35
- caption=[],
36
- )
37
- block = CreateImageBlock(image=image_data)
38
- context.result_blocks.append(block)
39
-
40
- def _extract_url(self, line: str) -> str | None:
41
- match = self._syntax.regex_pattern.search(line)
42
- return match.group(1).strip() if match else None
14
+ def _create_block(self, file_data: ExternalFileWithCaption) -> CreateImageBlock:
15
+ return CreateImageBlock(image=file_data)
@@ -1,42 +1,15 @@
1
- """Parser for PDF blocks."""
2
-
3
1
  from typing import override
4
2
 
5
- from notionary.blocks.schemas import (
6
- CreatePdfBlock,
7
- ExternalFile,
8
- FileData,
9
- FileType,
10
- )
11
- from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
- from notionary.page.content.syntax import SyntaxRegistry
13
-
3
+ from notionary.blocks.schemas import CreatePdfBlock, ExternalFileWithCaption
4
+ from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
5
+ from notionary.page.content.syntax import SyntaxDefinition, SyntaxRegistry
14
6
 
15
- class PdfParser(LineParser):
16
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
- super().__init__(syntax_registry)
18
- self._syntax = syntax_registry.get_pdf_syntax()
19
7
 
8
+ class PdfParser(FileLikeBlockParser[CreatePdfBlock]):
20
9
  @override
21
- def _can_handle(self, context: BlockParsingContext) -> bool:
22
- if context.is_inside_parent_context():
23
- return False
24
- return self._syntax.regex_pattern.search(context.line) is not None
10
+ def _get_syntax(self, syntax_registry: SyntaxRegistry) -> SyntaxDefinition:
11
+ return syntax_registry.get_pdf_syntax()
25
12
 
26
13
  @override
27
- async def _process(self, context: BlockParsingContext) -> None:
28
- url = self._extract_url(context.line)
29
- if not url:
30
- return
31
-
32
- pdf_data = FileData(
33
- type=FileType.EXTERNAL,
34
- external=ExternalFile(url=url),
35
- caption=[],
36
- )
37
- block = CreatePdfBlock(pdf=pdf_data)
38
- context.result_blocks.append(block)
39
-
40
- def _extract_url(self, line: str) -> str | None:
41
- match = self._syntax.regex_pattern.search(line)
42
- return match.group(1).strip() if match else None
14
+ def _create_block(self, file_data: ExternalFileWithCaption) -> CreatePdfBlock:
15
+ return CreatePdfBlock(pdf=file_data)
@@ -1,42 +1,15 @@
1
- """Parser for video blocks."""
2
-
3
1
  from typing import override
4
2
 
5
- from notionary.blocks.schemas import (
6
- CreateVideoBlock,
7
- ExternalFile,
8
- FileData,
9
- FileType,
10
- )
11
- from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
- from notionary.page.content.syntax import SyntaxRegistry
13
-
3
+ from notionary.blocks.schemas import CreateVideoBlock, ExternalFileWithCaption
4
+ from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
5
+ from notionary.page.content.syntax import SyntaxDefinition, SyntaxRegistry
14
6
 
15
- class VideoParser(LineParser):
16
- def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
- super().__init__(syntax_registry)
18
- self._syntax = syntax_registry.get_video_syntax()
19
7
 
8
+ class VideoParser(FileLikeBlockParser[CreateVideoBlock]):
20
9
  @override
21
- def _can_handle(self, context: BlockParsingContext) -> bool:
22
- if context.is_inside_parent_context():
23
- return False
24
- return self._syntax.regex_pattern.search(context.line) is not None
10
+ def _get_syntax(self, syntax_registry: SyntaxRegistry) -> SyntaxDefinition:
11
+ return syntax_registry.get_video_syntax()
25
12
 
26
13
  @override
27
- async def _process(self, context: BlockParsingContext) -> None:
28
- url = self._extract_url(context.line)
29
- if not url:
30
- return
31
-
32
- video_data = FileData(
33
- type=FileType.EXTERNAL,
34
- external=ExternalFile(url=url),
35
- caption=[],
36
- )
37
- block = CreateVideoBlock(video=video_data)
38
- context.result_blocks.append(block)
39
-
40
- def _extract_url(self, line: str) -> str | None:
41
- match = self._syntax.regex_pattern.search(line)
42
- return match.group(1).strip() if match else None
14
+ def _create_block(self, file_data: ExternalFileWithCaption) -> CreateVideoBlock:
15
+ return CreateVideoBlock(video=file_data)
@@ -1,31 +1,19 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType
4
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
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
5
6
 
6
7
 
7
- class AudioRenderer(CaptionedBlockRenderer):
8
+ class AudioRenderer(FileLikeBlockRenderer):
8
9
  @override
9
10
  def _can_handle(self, block: Block) -> bool:
10
11
  return block.type == BlockType.AUDIO
11
12
 
12
13
  @override
13
- async def _render_main_content(self, block: Block) -> str:
14
- url = self._extract_audio_url(block)
14
+ def _get_syntax(self) -> SyntaxDefinition:
15
+ return self._syntax_registry.get_audio_syntax()
15
16
 
16
- if not url:
17
- return ""
18
-
19
- syntax = self._syntax_registry.get_audio_syntax()
20
- return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
-
22
- def _extract_audio_url(self, block: Block) -> str:
23
- if not block.audio:
24
- return ""
25
-
26
- if block.audio.external:
27
- return block.audio.external.url or ""
28
- elif block.audio.file:
29
- return block.audio.file.url or ""
30
-
31
- return ""
17
+ @override
18
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
+ return block.audio
@@ -1,34 +1,22 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType
4
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
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
5
6
 
6
7
 
7
- class FileRenderer(CaptionedBlockRenderer):
8
+ class FileRenderer(FileLikeBlockRenderer):
8
9
  @override
9
10
  def _can_handle(self, block: Block) -> bool:
10
11
  return block.type == BlockType.FILE
11
12
 
12
13
  @override
13
- async def _render_main_content(self, block: Block) -> str:
14
- url = self._extract_file_url(block)
14
+ def _get_syntax(self) -> SyntaxDefinition:
15
+ return self._syntax_registry.get_file_syntax()
15
16
 
16
- if not url:
17
- return ""
18
-
19
- syntax = self._syntax_registry.get_file_syntax()
20
- return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
-
22
- def _extract_file_url(self, block: Block) -> str:
23
- if not block.file:
24
- return ""
25
-
26
- if block.file.external:
27
- return block.file.external.url or ""
28
- elif block.file.file:
29
- return block.file.file.url or ""
30
-
31
- return ""
17
+ @override
18
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
+ return block.file
32
20
 
33
21
  def _extract_file_name(self, block: Block) -> str:
34
22
  if not block.file:
@@ -0,0 +1,43 @@
1
+ from abc import abstractmethod
2
+ from typing import override
3
+
4
+ from notionary.blocks.schemas import (
5
+ Block,
6
+ ExternalFileWithCaption,
7
+ NotionHostedFileWithCaption,
8
+ )
9
+ from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
10
+ from notionary.page.content.syntax import SyntaxDefinition
11
+
12
+
13
+ class FileLikeBlockRenderer(CaptionedBlockRenderer):
14
+ @abstractmethod
15
+ def _get_syntax(self) -> SyntaxDefinition:
16
+ pass
17
+
18
+ @abstractmethod
19
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
20
+ pass
21
+
22
+ @override
23
+ async def _render_main_content(self, block: Block) -> str:
24
+ url = self._extract_url(block)
25
+
26
+ if not url:
27
+ return ""
28
+
29
+ syntax = self._get_syntax()
30
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
31
+
32
+ def _extract_url(self, block: Block) -> str:
33
+ file_data = self._get_file_data(block)
34
+
35
+ if not file_data:
36
+ return ""
37
+
38
+ if isinstance(file_data, ExternalFileWithCaption):
39
+ return file_data.external.url or ""
40
+ elif isinstance(file_data, NotionHostedFileWithCaption):
41
+ return file_data.file.url or ""
42
+
43
+ return ""
@@ -1,31 +1,19 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType
4
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
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
5
6
 
6
7
 
7
- class ImageRenderer(CaptionedBlockRenderer):
8
+ class ImageRenderer(FileLikeBlockRenderer):
8
9
  @override
9
10
  def _can_handle(self, block: Block) -> bool:
10
11
  return block.type == BlockType.IMAGE
11
12
 
12
13
  @override
13
- async def _render_main_content(self, block: Block) -> str:
14
- url = self._extract_image_url(block)
14
+ def _get_syntax(self) -> SyntaxDefinition:
15
+ return self._syntax_registry.get_image_syntax()
15
16
 
16
- if not url:
17
- return ""
18
-
19
- syntax = self._syntax_registry.get_image_syntax()
20
- return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
-
22
- def _extract_image_url(self, block: Block) -> str:
23
- if not block.image:
24
- return ""
25
-
26
- if block.image.external:
27
- return block.image.external.url or ""
28
- elif block.image.file:
29
- return block.image.file.url or ""
30
-
31
- return ""
17
+ @override
18
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
+ return block.image
@@ -1,31 +1,19 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType
4
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
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
5
6
 
6
7
 
7
- class PdfRenderer(CaptionedBlockRenderer):
8
+ class PdfRenderer(FileLikeBlockRenderer):
8
9
  @override
9
10
  def _can_handle(self, block: Block) -> bool:
10
11
  return block.type == BlockType.PDF
11
12
 
12
13
  @override
13
- async def _render_main_content(self, block: Block) -> str:
14
- url = self._extract_pdf_url(block)
14
+ def _get_syntax(self) -> SyntaxDefinition:
15
+ return self._syntax_registry.get_pdf_syntax()
15
16
 
16
- if not url:
17
- return ""
18
-
19
- syntax = self._syntax_registry.get_pdf_syntax()
20
- return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
-
22
- def _extract_pdf_url(self, block: Block) -> str:
23
- if not block.pdf:
24
- return ""
25
-
26
- if block.pdf.external:
27
- return block.pdf.external.url or ""
28
- elif block.pdf.file:
29
- return block.pdf.file.url or ""
30
-
31
- return ""
17
+ @override
18
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
+ return block.pdf
@@ -1,31 +1,19 @@
1
1
  from typing import override
2
2
 
3
- from notionary.blocks.schemas import Block, BlockType
4
- from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
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
5
6
 
6
7
 
7
- class VideoRenderer(CaptionedBlockRenderer):
8
+ class VideoRenderer(FileLikeBlockRenderer):
8
9
  @override
9
10
  def _can_handle(self, block: Block) -> bool:
10
11
  return block.type == BlockType.VIDEO
11
12
 
12
13
  @override
13
- async def _render_main_content(self, block: Block) -> str:
14
- url = self._extract_video_url(block)
14
+ def _get_syntax(self) -> SyntaxDefinition:
15
+ return self._syntax_registry.get_video_syntax()
15
16
 
16
- if not url:
17
- return ""
18
-
19
- syntax = self._syntax_registry.get_video_syntax()
20
- return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
-
22
- def _extract_video_url(self, block: Block) -> str:
23
- if not block.video:
24
- return ""
25
-
26
- if block.video.external:
27
- return block.video.external.url or ""
28
- elif block.video.file:
29
- return block.video.file.url or ""
30
-
31
- return ""
17
+ @override
18
+ def _get_file_data(self, block: Block) -> ExternalFileWithCaption | NotionHostedFileWithCaption | None:
19
+ return block.video
@@ -1,4 +1,5 @@
1
1
  from .grammar import MarkdownGrammar
2
+ from .models import SyntaxDefinition
2
3
  from .registry import SyntaxRegistry
3
4
 
4
- __all__ = ["MarkdownGrammar", "SyntaxRegistry"]
5
+ __all__ = ["MarkdownGrammar", "SyntaxDefinition", "SyntaxRegistry"]
@@ -4,6 +4,8 @@ from notionary.page.content.syntax.grammar import MarkdownGrammar
4
4
  from notionary.page.content.syntax.models import SyntaxDefinition, SyntaxRegistryKey
5
5
 
6
6
 
7
+ # TODO: Add support for file upload in blocks for file types (refactor file types aswell)
8
+ # differentiate between external and uploaded files (file like blocks)
7
9
  class SyntaxRegistry:
8
10
  def __init__(self, markdown_markdown_grammar: MarkdownGrammar | None = None) -> None:
9
11
  self._markdown_grammar = markdown_markdown_grammar or MarkdownGrammar()
@@ -92,63 +94,79 @@ class SyntaxRegistry:
92
94
  def get_heading_syntax(self) -> SyntaxDefinition:
93
95
  return self._definitions[SyntaxRegistryKey.HEADING]
94
96
 
97
+ def _create_media_syntax(self, media_type: str, url_pattern: str | None = None) -> SyntaxDefinition:
98
+ url_pattern = url_pattern or "[^)]+"
99
+ return SyntaxDefinition(
100
+ start_delimiter=f"[{media_type}](",
101
+ end_delimiter=")",
102
+ regex_pattern=re.compile(rf"(?<!\!)\[{re.escape(media_type)}\]\(({url_pattern})\)"),
103
+ )
104
+
105
+ def _create_url_media_syntax(self, media_type: str) -> SyntaxDefinition:
106
+ return SyntaxDefinition(
107
+ start_delimiter=f"[{media_type}](",
108
+ end_delimiter=")",
109
+ regex_pattern=re.compile(rf"(?<!\!)\[{re.escape(media_type)}\]\((https?://[^\s)]+)\)"),
110
+ )
111
+
95
112
  def _register_defaults(self) -> None:
96
113
  self._register_audio_syntax()
97
- self._register_bookmark_syntax()
98
- self._register_image_syntax()
99
114
  self._register_video_syntax()
115
+ self._register_image_syntax()
100
116
  self._register_file_syntax()
101
117
  self._register_pdf_syntax()
118
+ self._register_bookmark_syntax()
119
+ self._register_embed_syntax()
102
120
 
103
- # List blocks
104
121
  self._register_bulleted_list_syntax()
105
122
  self._register_numbered_list_syntax()
106
123
  self._register_todo_syntax()
107
124
  self._register_todo_done_syntax()
108
125
 
109
- # Container blocks
110
126
  self._register_toggle_syntax()
111
127
  self._register_toggleable_heading_syntax()
112
128
  self._register_callout_syntax()
113
129
  self._register_quote_syntax()
114
130
  self._register_code_syntax()
115
131
 
116
- # Column layout blocks
117
132
  self._register_column_list_syntax()
118
133
  self._register_column_syntax()
119
134
 
120
135
  self._register_heading_1_syntax()
121
136
  self._register_heading_2_syntax()
122
137
  self._register_heading_3_syntax()
123
- self._register_heading_syntax() # Shared pattern for regular headings
138
+ self._register_heading_syntax()
124
139
 
125
140
  self._register_divider_syntax()
126
141
  self._register_breadcrumb_syntax()
127
142
  self._register_table_of_contents_syntax()
128
143
  self._register_equation_syntax()
129
- self._register_embed_syntax()
130
144
  self._register_table_syntax()
131
145
  self._register_table_row_syntax()
132
146
 
133
- # Post-processing and utility blocks
134
147
  self._register_caption_syntax()
135
148
  self._register_space_syntax()
136
149
 
137
150
  def _register_audio_syntax(self) -> None:
138
- definition = SyntaxDefinition(
139
- start_delimiter="[audio](",
140
- end_delimiter=")",
141
- regex_pattern=re.compile(r"\[audio\]\(([^)]+)\)"),
142
- )
143
- self._definitions[SyntaxRegistryKey.AUDIO] = definition
151
+ self._definitions[SyntaxRegistryKey.AUDIO] = self._create_media_syntax("audio")
152
+
153
+ def _register_video_syntax(self) -> None:
154
+ self._definitions[SyntaxRegistryKey.VIDEO] = self._create_media_syntax("video")
155
+
156
+ def _register_image_syntax(self) -> None:
157
+ self._definitions[SyntaxRegistryKey.IMAGE] = self._create_media_syntax("image")
158
+
159
+ def _register_file_syntax(self) -> None:
160
+ self._definitions[SyntaxRegistryKey.FILE] = self._create_media_syntax("file")
161
+
162
+ def _register_pdf_syntax(self) -> None:
163
+ self._definitions[SyntaxRegistryKey.PDF] = self._create_media_syntax("pdf")
144
164
 
145
165
  def _register_bookmark_syntax(self) -> None:
146
- definition = SyntaxDefinition(
147
- start_delimiter="[bookmark](",
148
- end_delimiter=")",
149
- regex_pattern=re.compile(r"\[bookmark\]\((https?://[^\s\"]+)\)"),
150
- )
151
- self._definitions[SyntaxRegistryKey.BOOKMARK] = definition
166
+ self._definitions[SyntaxRegistryKey.BOOKMARK] = self._create_url_media_syntax("bookmark")
167
+
168
+ def _register_embed_syntax(self) -> None:
169
+ self._definitions[SyntaxRegistryKey.EMBED] = self._create_url_media_syntax("embed")
152
170
 
153
171
  def _register_breadcrumb_syntax(self) -> None:
154
172
  definition = SyntaxDefinition(
@@ -217,14 +235,6 @@ class SyntaxRegistry:
217
235
  )
218
236
  self._definitions[SyntaxRegistryKey.DIVIDER] = definition
219
237
 
220
- def _register_embed_syntax(self) -> None:
221
- definition = SyntaxDefinition(
222
- start_delimiter="[embed](",
223
- end_delimiter=")",
224
- regex_pattern=re.compile(r"\[embed\]\((https?://[^\s)]+)\)"),
225
- )
226
- self._definitions[SyntaxRegistryKey.EMBED] = definition
227
-
228
238
  def _register_equation_syntax(self) -> None:
229
239
  definition = SyntaxDefinition(
230
240
  start_delimiter="$$",
@@ -233,14 +243,6 @@ class SyntaxRegistry:
233
243
  )
234
244
  self._definitions[SyntaxRegistryKey.EQUATION] = definition
235
245
 
236
- def _register_file_syntax(self) -> None:
237
- definition = SyntaxDefinition(
238
- start_delimiter="[file](",
239
- end_delimiter=")",
240
- regex_pattern=re.compile(r"\[file\]\(([^)]+)\)"),
241
- )
242
- self._definitions[SyntaxRegistryKey.FILE] = definition
243
-
244
246
  def _register_heading_1_syntax(self) -> None:
245
247
  definition = SyntaxDefinition(
246
248
  start_delimiter="# ",
@@ -265,14 +267,6 @@ class SyntaxRegistry:
265
267
  )
266
268
  self._definitions[SyntaxRegistryKey.HEADING_3] = definition
267
269
 
268
- def _register_image_syntax(self) -> None:
269
- definition = SyntaxDefinition(
270
- start_delimiter="[image](",
271
- end_delimiter=")",
272
- regex_pattern=re.compile(r"(?<!!)\[image\]\(([^)]+)\)"),
273
- )
274
- self._definitions[SyntaxRegistryKey.IMAGE] = definition
275
-
276
270
  def _register_numbered_list_syntax(self) -> None:
277
271
  definition = SyntaxDefinition(
278
272
  start_delimiter="1. ",
@@ -281,14 +275,6 @@ class SyntaxRegistry:
281
275
  )
282
276
  self._definitions[SyntaxRegistryKey.NUMBERED_LIST] = definition
283
277
 
284
- def _register_pdf_syntax(self) -> None:
285
- definition = SyntaxDefinition(
286
- start_delimiter="[pdf](",
287
- end_delimiter=")",
288
- regex_pattern=re.compile(r"\[pdf\]\(([^)]+)\)"),
289
- )
290
- self._definitions[SyntaxRegistryKey.PDF] = definition
291
-
292
278
  def _register_quote_syntax(self) -> None:
293
279
  definition = SyntaxDefinition(
294
280
  start_delimiter="> ",
@@ -360,14 +346,6 @@ class SyntaxRegistry:
360
346
  )
361
347
  self._definitions[SyntaxRegistryKey.TOGGLEABLE_HEADING] = definition
362
348
 
363
- def _register_video_syntax(self) -> None:
364
- definition = SyntaxDefinition(
365
- start_delimiter="[video](",
366
- end_delimiter=")",
367
- regex_pattern=re.compile(r"\[video\]\(([^)]+)\)"),
368
- )
369
- self._definitions[SyntaxRegistryKey.VIDEO] = definition
370
-
371
349
  def _register_caption_syntax(self) -> None:
372
350
  definition = SyntaxDefinition(
373
351
  start_delimiter="[caption]",
@@ -4,7 +4,7 @@ from pydantic import BaseModel
4
4
 
5
5
  from notionary.blocks.rich_text.models import RichText
6
6
  from notionary.http.client import NotionHttpClient
7
- from notionary.page.properties.models import (
7
+ from notionary.page.properties.schemas import (
8
8
  DateValue,
9
9
  PageCheckboxProperty,
10
10
  PageDateProperty,