notionary 0.3.1__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. notionary/__init__.py +49 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/enums.py +0 -6
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  5. notionary/blocks/rich_text/models.py +13 -4
  6. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  7. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  8. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  9. notionary/blocks/schemas.py +33 -78
  10. notionary/comments/client.py +19 -6
  11. notionary/comments/factory.py +10 -3
  12. notionary/comments/schemas.py +10 -31
  13. notionary/comments/service.py +12 -4
  14. notionary/data_source/http/data_source_instance_client.py +59 -17
  15. notionary/data_source/properties/schemas.py +156 -115
  16. notionary/data_source/query/builder.py +67 -18
  17. notionary/data_source/query/resolver.py +16 -5
  18. notionary/data_source/query/schema.py +24 -6
  19. notionary/data_source/query/validator.py +18 -6
  20. notionary/data_source/schema/registry.py +31 -12
  21. notionary/data_source/schema/service.py +66 -20
  22. notionary/data_source/schemas.py +2 -2
  23. notionary/data_source/service.py +103 -43
  24. notionary/database/client.py +27 -9
  25. notionary/database/database_metadata_update_client.py +12 -4
  26. notionary/database/schemas.py +2 -2
  27. notionary/database/service.py +14 -9
  28. notionary/exceptions/__init__.py +20 -4
  29. notionary/exceptions/api.py +2 -2
  30. notionary/exceptions/base.py +1 -1
  31. notionary/exceptions/block_parsing.py +9 -5
  32. notionary/exceptions/data_source/builder.py +13 -7
  33. notionary/exceptions/data_source/properties.py +6 -4
  34. notionary/exceptions/file_upload.py +76 -0
  35. notionary/exceptions/properties.py +7 -5
  36. notionary/exceptions/search.py +10 -6
  37. notionary/file_upload/__init__.py +4 -0
  38. notionary/file_upload/client.py +128 -210
  39. notionary/file_upload/config/__init__.py +17 -0
  40. notionary/file_upload/config/config.py +39 -0
  41. notionary/file_upload/config/constants.py +16 -0
  42. notionary/file_upload/file/reader.py +28 -0
  43. notionary/file_upload/query/__init__.py +7 -0
  44. notionary/file_upload/query/builder.py +58 -0
  45. notionary/file_upload/query/models.py +37 -0
  46. notionary/file_upload/schemas.py +80 -0
  47. notionary/file_upload/service.py +182 -291
  48. notionary/file_upload/validation/factory.py +66 -0
  49. notionary/file_upload/validation/impl/file_name_length.py +25 -0
  50. notionary/file_upload/validation/models.py +134 -0
  51. notionary/file_upload/validation/port.py +7 -0
  52. notionary/file_upload/validation/service.py +17 -0
  53. notionary/file_upload/validation/validators/__init__.py +11 -0
  54. notionary/file_upload/validation/validators/file_exists.py +15 -0
  55. notionary/file_upload/validation/validators/file_extension.py +131 -0
  56. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  57. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  58. notionary/http/client.py +33 -30
  59. notionary/page/content/__init__.py +9 -0
  60. notionary/page/content/factory.py +21 -7
  61. notionary/page/content/markdown/builder.py +85 -23
  62. notionary/page/content/markdown/nodes/audio.py +8 -4
  63. notionary/page/content/markdown/nodes/base.py +3 -3
  64. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  65. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  66. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  67. notionary/page/content/markdown/nodes/callout.py +2 -2
  68. notionary/page/content/markdown/nodes/code.py +5 -3
  69. notionary/page/content/markdown/nodes/columns.py +3 -3
  70. notionary/page/content/markdown/nodes/container.py +9 -5
  71. notionary/page/content/markdown/nodes/divider.py +2 -2
  72. notionary/page/content/markdown/nodes/embed.py +8 -4
  73. notionary/page/content/markdown/nodes/equation.py +4 -2
  74. notionary/page/content/markdown/nodes/file.py +8 -4
  75. notionary/page/content/markdown/nodes/heading.py +2 -2
  76. notionary/page/content/markdown/nodes/image.py +8 -4
  77. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  78. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  79. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  80. notionary/page/content/markdown/nodes/pdf.py +8 -4
  81. notionary/page/content/markdown/nodes/quote.py +2 -2
  82. notionary/page/content/markdown/nodes/space.py +2 -2
  83. notionary/page/content/markdown/nodes/table.py +8 -5
  84. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  85. notionary/page/content/markdown/nodes/todo.py +15 -7
  86. notionary/page/content/markdown/nodes/toggle.py +2 -2
  87. notionary/page/content/markdown/nodes/video.py +8 -4
  88. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  89. notionary/page/content/markdown/structured_output/models.py +391 -0
  90. notionary/page/content/markdown/structured_output/service.py +211 -0
  91. notionary/page/content/parser/context.py +1 -1
  92. notionary/page/content/parser/factory.py +26 -8
  93. notionary/page/content/parser/parsers/audio.py +12 -32
  94. notionary/page/content/parser/parsers/base.py +2 -2
  95. notionary/page/content/parser/parsers/bookmark.py +2 -2
  96. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  97. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  98. notionary/page/content/parser/parsers/callout.py +15 -5
  99. notionary/page/content/parser/parsers/caption.py +9 -3
  100. notionary/page/content/parser/parsers/code.py +21 -7
  101. notionary/page/content/parser/parsers/column.py +8 -4
  102. notionary/page/content/parser/parsers/column_list.py +19 -7
  103. notionary/page/content/parser/parsers/divider.py +2 -2
  104. notionary/page/content/parser/parsers/embed.py +2 -4
  105. notionary/page/content/parser/parsers/equation.py +8 -4
  106. notionary/page/content/parser/parsers/file.py +12 -34
  107. notionary/page/content/parser/parsers/file_like_block.py +109 -0
  108. notionary/page/content/parser/parsers/heading.py +31 -10
  109. notionary/page/content/parser/parsers/image.py +12 -34
  110. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  111. notionary/page/content/parser/parsers/paragraph.py +3 -1
  112. notionary/page/content/parser/parsers/pdf.py +12 -34
  113. notionary/page/content/parser/parsers/quote.py +28 -9
  114. notionary/page/content/parser/parsers/space.py +2 -2
  115. notionary/page/content/parser/parsers/table.py +31 -10
  116. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  117. notionary/page/content/parser/parsers/todo.py +15 -5
  118. notionary/page/content/parser/parsers/toggle.py +15 -5
  119. notionary/page/content/parser/parsers/video.py +12 -34
  120. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  121. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  122. notionary/page/content/parser/post_processing/service.py +3 -1
  123. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  124. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  125. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  126. notionary/page/content/parser/service.py +4 -1
  127. notionary/page/content/renderer/context.py +15 -5
  128. notionary/page/content/renderer/factory.py +12 -6
  129. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  130. notionary/page/content/renderer/renderers/audio.py +20 -23
  131. notionary/page/content/renderer/renderers/base.py +3 -3
  132. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  133. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  134. notionary/page/content/renderer/renderers/callout.py +19 -7
  135. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  136. notionary/page/content/renderer/renderers/code.py +6 -2
  137. notionary/page/content/renderer/renderers/column.py +3 -1
  138. notionary/page/content/renderer/renderers/column_list.py +3 -1
  139. notionary/page/content/renderer/renderers/embed.py +3 -1
  140. notionary/page/content/renderer/renderers/equation.py +3 -1
  141. notionary/page/content/renderer/renderers/file.py +20 -23
  142. notionary/page/content/renderer/renderers/file_like_block.py +47 -0
  143. notionary/page/content/renderer/renderers/heading.py +22 -8
  144. notionary/page/content/renderer/renderers/image.py +20 -23
  145. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  146. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  147. notionary/page/content/renderer/renderers/pdf.py +20 -23
  148. notionary/page/content/renderer/renderers/quote.py +14 -6
  149. notionary/page/content/renderer/renderers/table.py +15 -5
  150. notionary/page/content/renderer/renderers/todo.py +16 -6
  151. notionary/page/content/renderer/renderers/toggle.py +8 -4
  152. notionary/page/content/renderer/renderers/video.py +20 -23
  153. notionary/page/content/renderer/service.py +9 -3
  154. notionary/page/content/service.py +21 -7
  155. notionary/page/content/syntax/definition/__init__.py +11 -0
  156. notionary/page/content/syntax/definition/models.py +57 -0
  157. notionary/page/content/syntax/definition/registry.py +371 -0
  158. notionary/page/content/syntax/prompts/__init__.py +4 -0
  159. notionary/page/content/syntax/prompts/models.py +11 -0
  160. notionary/page/content/syntax/prompts/registry.py +703 -0
  161. notionary/page/page_metadata_update_client.py +12 -4
  162. notionary/page/properties/client.py +46 -16
  163. notionary/page/properties/factory.py +6 -2
  164. notionary/page/properties/{models.py → schemas.py} +93 -107
  165. notionary/page/properties/service.py +111 -37
  166. notionary/page/schemas.py +3 -3
  167. notionary/page/service.py +21 -7
  168. notionary/shared/entity/client.py +6 -2
  169. notionary/shared/entity/dto_parsers.py +4 -37
  170. notionary/shared/entity/entity_metadata_update_client.py +25 -5
  171. notionary/shared/entity/schemas.py +6 -6
  172. notionary/shared/entity/service.py +89 -35
  173. notionary/shared/models/file.py +36 -6
  174. notionary/shared/models/icon.py +5 -12
  175. notionary/user/base.py +6 -2
  176. notionary/user/bot.py +22 -14
  177. notionary/user/client.py +3 -1
  178. notionary/user/person.py +3 -1
  179. notionary/user/schemas.py +3 -1
  180. notionary/user/service.py +6 -2
  181. notionary/utils/decorators.py +13 -9
  182. notionary/utils/fuzzy.py +6 -2
  183. notionary/utils/mixins/logging.py +3 -1
  184. notionary/utils/pagination.py +14 -4
  185. notionary/workspace/__init__.py +6 -2
  186. notionary/workspace/query/__init__.py +2 -1
  187. notionary/workspace/query/service.py +42 -13
  188. notionary/workspace/service.py +74 -46
  189. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
  190. notionary-0.4.1.dist-info/RECORD +236 -0
  191. notionary/file_upload/models.py +0 -69
  192. notionary/page/blocks/client.py +0 -1
  193. notionary/page/content/syntax/__init__.py +0 -4
  194. notionary/page/content/syntax/models.py +0 -66
  195. notionary/page/content/syntax/registry.py +0 -393
  196. notionary/page/page_context.py +0 -50
  197. notionary/shared/models/cover.py +0 -20
  198. notionary-0.3.1.dist-info/RECORD +0 -211
  199. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  200. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
  201. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
notionary/__init__.py CHANGED
@@ -1,14 +1,62 @@
1
1
  from .data_source.service import NotionDataSource
2
2
  from .database.service import NotionDatabase
3
+ from .file_upload import FileUploadQuery, FileUploadQueryBuilder, NotionFileUpload
4
+ from .page.content import SyntaxPromptRegistry
3
5
  from .page.content.markdown.builder import MarkdownBuilder
6
+ from .page.content.markdown.structured_output import (
7
+ MarkdownDocumentSchema,
8
+ StructuredOutputMarkdownConverter,
9
+ )
4
10
  from .page.service import NotionPage
5
- from .workspace import NotionWorkspace, NotionWorkspaceQueryConfigBuilder
11
+ from .workspace import (
12
+ NotionWorkspace,
13
+ NotionWorkspaceQueryConfigBuilder,
14
+ WorkspaceQueryConfig,
15
+ )
6
16
 
7
17
  __all__ = [
18
+ "AudioSchema",
19
+ "BookmarkSchema",
20
+ "BreadcrumbSchema",
21
+ "BulletedListItemSchema",
22
+ "BulletedListSchema",
23
+ "CalloutSchema",
24
+ "CodeSchema",
25
+ "ColumnSchema",
26
+ "ColumnsSchema",
27
+ "DividerSchema",
28
+ "EmbedSchema",
29
+ "EquationSchema",
30
+ "FileSchema",
31
+ "FileUploadQuery",
32
+ "FileUploadQueryBuilder",
33
+ "Heading1Schema",
34
+ "Heading2Schema",
35
+ "Heading3Schema",
36
+ "ImageSchema",
8
37
  "MarkdownBuilder",
38
+ "MarkdownDocumentSchema",
39
+ "MarkdownNodeSchema",
40
+ "MermaidSchema",
9
41
  "NotionDataSource",
10
42
  "NotionDatabase",
43
+ "NotionFileUpload",
11
44
  "NotionPage",
12
45
  "NotionWorkspace",
13
46
  "NotionWorkspaceQueryConfigBuilder",
47
+ "NumberedListItemSchema",
48
+ "NumberedListSchema",
49
+ "ParagraphSchema",
50
+ "PdfSchema",
51
+ "QuoteSchema",
52
+ "SpaceSchema",
53
+ "StructuredOutputMarkdownConverter",
54
+ "SyntaxPromptRegistry",
55
+ "TableOfContentsSchema",
56
+ "TableSchema",
57
+ "TodoListSchema",
58
+ "TodoSchema",
59
+ "ToggleSchema",
60
+ "VideoSchema",
61
+ "WorkspaceQueryConfig",
14
62
  ]
@@ -31,9 +31,13 @@ class NotionBlockHttpClient(NotionHttpClient):
31
31
  async def get_all_block_children(self, parent_block_id: str) -> list[Block]:
32
32
  self.logger.debug("Retrieving all children for block: %s", parent_block_id)
33
33
 
34
- all_blocks = await paginate_notion_api(self.get_block_children, block_id=parent_block_id)
34
+ all_blocks = await paginate_notion_api(
35
+ self.get_block_children, block_id=parent_block_id
36
+ )
35
37
 
36
- self.logger.debug("Retrieved %d total children for block %s", len(all_blocks), parent_block_id)
38
+ self.logger.debug(
39
+ "Retrieved %d total children for block %s", len(all_blocks), parent_block_id
40
+ )
37
41
  return all_blocks
38
42
 
39
43
  async def get_block_children(
@@ -64,11 +68,17 @@ class NotionBlockHttpClient(NotionHttpClient):
64
68
 
65
69
  if len(batches) == 1:
66
70
  children_dicts = self._serialize_blocks(batches[0])
67
- return await self._send_append_request(block_id, children_dicts, insert_after_block_id)
71
+ return await self._send_append_request(
72
+ block_id, children_dicts, insert_after_block_id
73
+ )
68
74
 
69
- return await self._send_batched_append_requests(block_id, batches, insert_after_block_id)
75
+ return await self._send_batched_append_requests(
76
+ block_id, batches, insert_after_block_id
77
+ )
70
78
 
71
- def _split_into_batches(self, blocks: list[BlockCreatePayload]) -> list[list[BlockCreatePayload]]:
79
+ def _split_into_batches(
80
+ self, blocks: list[BlockCreatePayload]
81
+ ) -> list[list[BlockCreatePayload]]:
72
82
  batches = []
73
83
  for i in range(0, len(blocks), self.BATCH_SIZE):
74
84
  batch = blocks[i : i + self.BATCH_SIZE]
@@ -89,19 +99,31 @@ class NotionBlockHttpClient(NotionHttpClient):
89
99
  return BlockChildrenResponse.model_validate(response)
90
100
 
91
101
  async def _send_batched_append_requests(
92
- self, block_id: str, batches: list[list[BlockCreatePayload]], initial_after_block_id: str | None = None
102
+ self,
103
+ block_id: str,
104
+ batches: list[list[BlockCreatePayload]],
105
+ initial_after_block_id: str | None = None,
93
106
  ) -> BlockChildrenResponse:
94
107
  total_blocks = sum(len(batch) for batch in batches)
95
- self.logger.info("Appending %d blocks in %d batches", total_blocks, len(batches))
108
+ self.logger.info(
109
+ "Appending %d blocks in %d batches", total_blocks, len(batches)
110
+ )
96
111
 
97
112
  all_responses = []
98
113
  after_block_id = initial_after_block_id
99
114
 
100
115
  for batch_index, batch in enumerate(batches, start=1):
101
- self.logger.debug("Processing batch %d/%d (%d blocks)", batch_index, len(batches), len(batch))
116
+ self.logger.debug(
117
+ "Processing batch %d/%d (%d blocks)",
118
+ batch_index,
119
+ len(batches),
120
+ len(batch),
121
+ )
102
122
 
103
123
  children_dicts = self._serialize_blocks(batch)
104
- response = await self._send_append_request(block_id, children_dicts, after_block_id)
124
+ response = await self._send_append_request(
125
+ block_id, children_dicts, after_block_id
126
+ )
105
127
  all_responses.append(response)
106
128
 
107
129
  if response.results:
@@ -112,9 +134,13 @@ class NotionBlockHttpClient(NotionHttpClient):
112
134
  self.logger.info("Successfully appended all blocks in %d batches", len(batches))
113
135
  return self._merge_responses(all_responses)
114
136
 
115
- def _merge_responses(self, responses: list[BlockChildrenResponse]) -> BlockChildrenResponse:
137
+ def _merge_responses(
138
+ self, responses: list[BlockChildrenResponse]
139
+ ) -> BlockChildrenResponse:
116
140
  if not responses:
117
- raise ValueError("Cannot merge empty response list - this should never happen")
141
+ raise ValueError(
142
+ "Cannot merge empty response list - this should never happen"
143
+ )
118
144
 
119
145
  first_response = responses[0]
120
146
  all_results = [block for response in responses for block in response.results]
notionary/blocks/enums.py CHANGED
@@ -62,12 +62,6 @@ class BlockType(StrEnum):
62
62
  VIDEO = "video"
63
63
 
64
64
 
65
- class FileType(StrEnum):
66
- EXTERNAL = "external"
67
- FILE = "file"
68
- FILE_UPLOAD = "file_upload"
69
-
70
-
71
65
  class CodingLanguage(StrEnum):
72
66
  ABAP = "abap"
73
67
  ARDUINO = "arduino"
@@ -4,7 +4,12 @@ from dataclasses import dataclass
4
4
  from re import Match
5
5
  from typing import ClassVar
6
6
 
7
- from notionary.blocks.rich_text.models import MentionType, RichText, RichTextType, TextAnnotations
7
+ from notionary.blocks.rich_text.models import (
8
+ MentionType,
9
+ RichText,
10
+ RichTextType,
11
+ TextAnnotations,
12
+ )
8
13
  from notionary.blocks.rich_text.name_id_resolver import (
9
14
  DatabaseNameIdResolver,
10
15
  DataSourceNameIdResolver,
@@ -58,17 +63,32 @@ class MarkdownRichTextConverter:
58
63
  return [
59
64
  PatternHandler(RichTextPatterns.BOLD, self._handle_bold_pattern),
60
65
  PatternHandler(RichTextPatterns.ITALIC, self._handle_italic_pattern),
61
- PatternHandler(RichTextPatterns.ITALIC_UNDERSCORE, self._handle_italic_pattern),
66
+ PatternHandler(
67
+ RichTextPatterns.ITALIC_UNDERSCORE, self._handle_italic_pattern
68
+ ),
62
69
  PatternHandler(RichTextPatterns.UNDERLINE, self._handle_underline_pattern),
63
- PatternHandler(RichTextPatterns.STRIKETHROUGH, self._handle_strikethrough_pattern),
70
+ PatternHandler(
71
+ RichTextPatterns.STRIKETHROUGH, self._handle_strikethrough_pattern
72
+ ),
64
73
  PatternHandler(RichTextPatterns.CODE, self._handle_code_pattern),
65
74
  PatternHandler(RichTextPatterns.LINK, self._handle_link_pattern),
66
- PatternHandler(RichTextPatterns.INLINE_EQUATION, self._handle_equation_pattern),
75
+ PatternHandler(
76
+ RichTextPatterns.INLINE_EQUATION, self._handle_equation_pattern
77
+ ),
67
78
  PatternHandler(RichTextPatterns.COLOR, self._handle_color_pattern),
68
- PatternHandler(RichTextPatterns.PAGE_MENTION, self._handle_page_mention_pattern),
69
- PatternHandler(RichTextPatterns.DATABASE_MENTION, self._handle_database_mention_pattern),
70
- PatternHandler(RichTextPatterns.DATASOURCE_MENTION, self._handle_data_source_mention_pattern),
71
- PatternHandler(RichTextPatterns.USER_MENTION, self._handle_user_mention_pattern),
79
+ PatternHandler(
80
+ RichTextPatterns.PAGE_MENTION, self._handle_page_mention_pattern
81
+ ),
82
+ PatternHandler(
83
+ RichTextPatterns.DATABASE_MENTION, self._handle_database_mention_pattern
84
+ ),
85
+ PatternHandler(
86
+ RichTextPatterns.DATASOURCE_MENTION,
87
+ self._handle_data_source_mention_pattern,
88
+ ),
89
+ PatternHandler(
90
+ RichTextPatterns.USER_MENTION, self._handle_user_mention_pattern
91
+ ),
72
92
  ]
73
93
 
74
94
  async def to_rich_text(self, text: str) -> list[RichText]:
@@ -106,12 +126,16 @@ class MarkdownRichTextConverter:
106
126
  for pattern_handler in self.format_handlers:
107
127
  match = re.search(pattern_handler.pattern, text)
108
128
  if match and match.start() < earliest_position:
109
- earliest_match = PatternMatch(match=match, handler=pattern_handler.handler, position=match.start())
129
+ earliest_match = PatternMatch(
130
+ match=match, handler=pattern_handler.handler, position=match.start()
131
+ )
110
132
  earliest_position = match.start()
111
133
 
112
134
  return earliest_match
113
135
 
114
- async def _process_pattern_match(self, pattern_match: PatternMatch) -> RichText | list[RichText]:
136
+ async def _process_pattern_match(
137
+ self, pattern_match: PatternMatch
138
+ ) -> RichText | list[RichText]:
115
139
  handler_method = pattern_match.handler
116
140
 
117
141
  if self._is_async_handler(handler_method):
@@ -169,9 +193,13 @@ class MarkdownRichTextConverter:
169
193
  def _apply_color_to_link_segment(self, segment: RichText, color: str) -> RichText:
170
194
  formatting = self._extract_formatting_attributes(segment.annotations)
171
195
 
172
- return RichText.for_link(segment.plain_text, segment.text.link.url, color=color, **formatting)
196
+ return RichText.for_link(
197
+ segment.plain_text, segment.text.link.url, color=color, **formatting
198
+ )
173
199
 
174
- def _apply_color_to_plain_text_segment(self, segment: RichText, color: str) -> RichText:
200
+ def _apply_color_to_plain_text_segment(
201
+ self, segment: RichText, color: str
202
+ ) -> RichText:
175
203
  if segment.type != RichTextType.TEXT:
176
204
  return segment
177
205
 
@@ -179,7 +207,9 @@ class MarkdownRichTextConverter:
179
207
 
180
208
  return RichText.from_plain_text(segment.plain_text, color=color, **formatting)
181
209
 
182
- def _extract_formatting_attributes(self, annotations: TextAnnotations) -> dict[str, bool]:
210
+ def _extract_formatting_attributes(
211
+ self, annotations: TextAnnotations
212
+ ) -> dict[str, bool]:
183
213
  if not annotations:
184
214
  return {
185
215
  "bold": False,
@@ -246,13 +276,17 @@ class MarkdownRichTextConverter:
246
276
  if resolved_id:
247
277
  return create_mention_func(resolved_id)
248
278
  else:
249
- return self._create_unresolved_mention_fallback(identifier, mention_type)
279
+ return self._create_unresolved_mention_fallback(
280
+ identifier, mention_type
281
+ )
250
282
 
251
283
  except Exception:
252
284
  # If resolution throws an error, fallback to plain text
253
285
  return self._create_unresolved_mention_fallback(identifier, mention_type)
254
286
 
255
- def _create_unresolved_mention_fallback(self, identifier: str, mention_type: MentionType) -> RichText:
287
+ def _create_unresolved_mention_fallback(
288
+ self, identifier: str, mention_type: MentionType
289
+ ) -> RichText:
256
290
  fallback_text = f"@{mention_type.value}[{identifier}]"
257
291
  return RichText.for_caption(fallback_text)
258
292
 
@@ -140,7 +140,9 @@ class RichText(BaseModel):
140
140
  def mention_user(cls, user_id: str) -> Self:
141
141
  return cls(
142
142
  type=RichTextType.MENTION,
143
- mention=MentionObject(type=MentionType.USER, user=MentionUserRef(id=user_id)),
143
+ mention=MentionObject(
144
+ type=MentionType.USER, user=MentionUserRef(id=user_id)
145
+ ),
144
146
  annotations=TextAnnotations(),
145
147
  )
146
148
 
@@ -148,7 +150,9 @@ class RichText(BaseModel):
148
150
  def mention_page(cls, page_id: str) -> Self:
149
151
  return cls(
150
152
  type=RichTextType.MENTION,
151
- mention=MentionObject(type=MentionType.PAGE, page=MentionPageRef(id=page_id)),
153
+ mention=MentionObject(
154
+ type=MentionType.PAGE, page=MentionPageRef(id=page_id)
155
+ ),
152
156
  annotations=TextAnnotations(),
153
157
  )
154
158
 
@@ -156,7 +160,9 @@ class RichText(BaseModel):
156
160
  def mention_database(cls, database_id: str) -> Self:
157
161
  return cls(
158
162
  type=RichTextType.MENTION,
159
- mention=MentionObject(type=MentionType.DATABASE, database=MentionDatabaseRef(id=database_id)),
163
+ mention=MentionObject(
164
+ type=MentionType.DATABASE, database=MentionDatabaseRef(id=database_id)
165
+ ),
160
166
  annotations=TextAnnotations(),
161
167
  )
162
168
 
@@ -164,7 +170,10 @@ class RichText(BaseModel):
164
170
  def mention_data_source(cls, data_source_id: str) -> Self:
165
171
  return cls(
166
172
  type=RichTextType.MENTION,
167
- mention=MentionObject(type=MentionType.DATASOURCE, data_source=MentionDataSourceRef(id=data_source_id)),
173
+ mention=MentionObject(
174
+ type=MentionType.DATASOURCE,
175
+ data_source=MentionDataSourceRef(id=data_source_id),
176
+ ),
168
177
  annotations=TextAnnotations(),
169
178
  )
170
179
 
@@ -6,8 +6,12 @@ from notionary.workspace.query.service import WorkspaceQueryService
6
6
 
7
7
  # !!! in the notion api mentions that reference datasources are not provided yet (it's a limiation of the API as of now)
8
8
  class DataSourceNameIdResolver(NameIdResolver):
9
- def __init__(self, workspace_query_service: WorkspaceQueryService | None = None) -> None:
10
- self._workspace_query_service = workspace_query_service or WorkspaceQueryService()
9
+ def __init__(
10
+ self, workspace_query_service: WorkspaceQueryService | None = None
11
+ ) -> None:
12
+ self._workspace_query_service = (
13
+ workspace_query_service or WorkspaceQueryService()
14
+ )
11
15
 
12
16
  @override
13
17
  async def resolve_name_to_id(self, name: str) -> str | None:
@@ -15,7 +19,9 @@ class DataSourceNameIdResolver(NameIdResolver):
15
19
  return None
16
20
 
17
21
  cleaned_name = name.strip()
18
- data_source = await self._workspace_query_service.find_data_source(query=cleaned_name)
22
+ data_source = await self._workspace_query_service.find_data_source(
23
+ query=cleaned_name
24
+ )
19
25
  return data_source.id if data_source else None
20
26
 
21
27
  @override
@@ -6,7 +6,9 @@ from notionary.user.person import PersonUser
6
6
 
7
7
 
8
8
  class PersonNameIdResolver(NameIdResolver):
9
- def __init__(self, person_user_factory=None, http_client: UserHttpClient | None = None) -> None:
9
+ def __init__(
10
+ self, person_user_factory=None, http_client: UserHttpClient | None = None
11
+ ) -> None:
10
12
  if person_user_factory is None:
11
13
  person_user_factory = PersonUser
12
14
  self.person_user_factory = person_user_factory
@@ -31,7 +33,9 @@ class PersonNameIdResolver(NameIdResolver):
31
33
  return None
32
34
 
33
35
  try:
34
- user = await self.person_user_factory.from_id(user_id.strip(), self.http_client)
36
+ user = await self.person_user_factory.from_id(
37
+ user_id.strip(), self.http_client
38
+ )
35
39
  return user.name if user else None
36
40
  except Exception:
37
41
  return None
@@ -69,7 +69,9 @@ class RichTextToMarkdownConverter:
69
69
  return await self._extract_database_mention_markdown(mention.database.id)
70
70
 
71
71
  elif mention.type == MentionType.DATASOURCE and mention.data_source:
72
- return await self._extract_data_source_mention_markdown(mention.data_source.id)
72
+ return await self._extract_data_source_mention_markdown(
73
+ mention.data_source.id
74
+ )
73
75
 
74
76
  elif mention.type == MentionType.USER and mention.user:
75
77
  return await self._extract_user_mention_markdown(mention.user.id)
@@ -88,7 +90,9 @@ class RichTextToMarkdownConverter:
88
90
  return f"@database[{database_name or database_id}]"
89
91
 
90
92
  async def _extract_data_source_mention_markdown(self, data_source_id: str) -> str:
91
- data_source_name = await self.data_source_resolver.resolve_id_to_name(data_source_id)
93
+ data_source_name = await self.data_source_resolver.resolve_id_to_name(
94
+ data_source_id
95
+ )
92
96
  return f"@datasource[{data_source_name or data_source_id}]"
93
97
 
94
98
  async def _extract_user_mention_markdown(self, user_id: str) -> str:
@@ -121,7 +125,10 @@ class RichTextToMarkdownConverter:
121
125
  if annotations.bold:
122
126
  content = f"**{content}**"
123
127
 
124
- if annotations.color != BlockColor.DEFAULT and annotations.color in self.VALID_COLORS:
128
+ if (
129
+ annotations.color != BlockColor.DEFAULT
130
+ and annotations.color in self.VALID_COLORS
131
+ ):
125
132
  content = f"({annotations.color}:{content})"
126
133
 
127
134
  return content
@@ -4,41 +4,39 @@ from typing import Annotated, Literal
4
4
 
5
5
  from pydantic import BaseModel, ConfigDict, Field
6
6
 
7
- from notionary.blocks.enums import BlockColor, BlockType, CodingLanguage, FileType
7
+ from notionary.blocks.enums import BlockColor, BlockType, CodingLanguage
8
8
  from notionary.blocks.rich_text.models import RichText
9
+ from notionary.shared.models.file import ExternalFile, FileUploadFile, NotionHostedFile
9
10
  from notionary.shared.models.icon import Icon
10
11
  from notionary.shared.models.parent import Parent
11
12
  from notionary.user.schemas import PartialUserDto
12
13
 
13
14
  # ============================================================================
14
- # File-related models
15
+ # File Data wrapper with caption
15
16
  # ============================================================================
16
17
 
17
18
 
18
- class ExternalFile(BaseModel):
19
- model_config = ConfigDict(from_attributes=True)
20
- url: str
19
+ class CaptionMixin(BaseModel):
20
+ caption: list[RichText] = Field(default_factory=list)
21
+ name: str | None = None
21
22
 
22
23
 
23
- class NotionHostedFile(BaseModel):
24
- model_config = ConfigDict(from_attributes=True)
25
- url: str
26
- expiry_time: str
24
+ class ExternalFileWithCaption(CaptionMixin, ExternalFile):
25
+ pass
27
26
 
28
27
 
29
- class FileUploadFile(BaseModel):
30
- model_config = ConfigDict(from_attributes=True)
31
- id: str
28
+ class NotionHostedFileWithCaption(CaptionMixin, NotionHostedFile):
29
+ pass
32
30
 
33
31
 
34
- class FileData(BaseModel):
35
- model_config = ConfigDict(from_attributes=True)
36
- caption: list[RichText] = Field(default_factory=list)
37
- type: FileType
38
- external: ExternalFile | None = None
39
- file: NotionHostedFile | None = None
40
- file_upload: FileUploadFile | None = None
41
- name: str | None = None
32
+ class FileUploadFileWithCaption(CaptionMixin, FileUploadFile):
33
+ pass
34
+
35
+
36
+ type FileWithCaption = Annotated[
37
+ ExternalFileWithCaption | NotionHostedFileWithCaption | FileUploadFileWithCaption,
38
+ Field(discriminator="type"),
39
+ ]
42
40
 
43
41
 
44
42
  # ============================================================================
@@ -65,24 +63,14 @@ class BaseBlock(BaseModel):
65
63
  # ============================================================================
66
64
 
67
65
 
68
- class AudioData(BaseModel):
69
- model_config = ConfigDict(from_attributes=True)
70
- caption: list[RichText] = Field(default_factory=list)
71
- type: FileType
72
- external: ExternalFile | None = None
73
- file: NotionHostedFile | None = None
74
- file_upload: FileUploadFile | None = None
75
- name: str | None = None
76
-
77
-
78
66
  class AudioBlock(BaseBlock):
79
67
  type: Literal[BlockType.AUDIO] = BlockType.AUDIO
80
- audio: AudioData
68
+ audio: FileWithCaption
81
69
 
82
70
 
83
71
  class CreateAudioBlock(BaseModel):
84
72
  type: Literal[BlockType.AUDIO] = BlockType.AUDIO
85
- audio: AudioData
73
+ audio: FileWithCaption
86
74
 
87
75
 
88
76
  # ============================================================================
@@ -90,8 +78,7 @@ class CreateAudioBlock(BaseModel):
90
78
  # ============================================================================
91
79
 
92
80
 
93
- class BookmarkData(BaseModel):
94
- caption: list[RichText] = Field(default_factory=list)
81
+ class BookmarkData(CaptionMixin):
95
82
  url: str
96
83
 
97
84
 
@@ -224,8 +211,7 @@ class CreateChildDatabaseBlock(BaseModel):
224
211
  # ============================================================================
225
212
 
226
213
 
227
- class CodeData(BaseModel):
228
- caption: list[RichText] = Field(default_factory=list)
214
+ class CodeData(CaptionMixin):
229
215
  rich_text: list[RichText]
230
216
  language: CodingLanguage = CodingLanguage.PLAIN_TEXT
231
217
 
@@ -311,9 +297,8 @@ class CreateDividerBlock(BaseModel):
311
297
  # ============================================================================
312
298
 
313
299
 
314
- class EmbedData(BaseModel):
300
+ class EmbedData(CaptionMixin):
315
301
  url: str
316
- caption: list[RichText] = Field(default_factory=list)
317
302
 
318
303
 
319
304
  class EmbedBlock(BaseBlock):
@@ -352,12 +337,12 @@ class CreateEquationBlock(BaseModel):
352
337
 
353
338
  class FileBlock(BaseBlock):
354
339
  type: Literal[BlockType.FILE] = BlockType.FILE
355
- file: FileData
340
+ file: FileWithCaption
356
341
 
357
342
 
358
343
  class CreateFileBlock(BaseModel):
359
344
  type: Literal[BlockType.FILE] = BlockType.FILE
360
- file: FileData
345
+ file: FileWithCaption
361
346
 
362
347
 
363
348
  # ============================================================================
@@ -416,24 +401,14 @@ CreateHeadingBlock = CreateHeading1Block | CreateHeading2Block | CreateHeading3B
416
401
  # ============================================================================
417
402
 
418
403
 
419
- class ImageData(BaseModel):
420
- model_config = ConfigDict(from_attributes=True)
421
- caption: list[RichText] = Field(default_factory=list)
422
- type: FileType
423
- external: ExternalFile | None = None
424
- file: NotionHostedFile | None = None
425
- file_upload: FileUploadFile | None = None
426
- name: str | None = None
427
-
428
-
429
404
  class ImageBlock(BaseBlock):
430
405
  type: Literal[BlockType.IMAGE] = BlockType.IMAGE
431
- image: ImageData
406
+ image: FileWithCaption
432
407
 
433
408
 
434
409
  class CreateImageBlock(BaseModel):
435
410
  type: Literal[BlockType.IMAGE] = BlockType.IMAGE
436
- image: ImageData
411
+ image: FileWithCaption
437
412
 
438
413
 
439
414
  # ============================================================================
@@ -497,24 +472,14 @@ class CreateParagraphBlock(BaseModel):
497
472
  # ============================================================================
498
473
 
499
474
 
500
- class PdfData(BaseModel):
501
- model_config = ConfigDict(from_attributes=True)
502
- caption: list[RichText] = Field(default_factory=list)
503
- type: FileType
504
- external: ExternalFile | None = None
505
- file: NotionHostedFile | None = None
506
- file_upload: FileUploadFile | None = None
507
- name: str | None = None
508
-
509
-
510
475
  class PdfBlock(BaseBlock):
511
476
  type: Literal[BlockType.PDF] = BlockType.PDF
512
- pdf: PdfData
477
+ pdf: FileWithCaption
513
478
 
514
479
 
515
480
  class CreatePdfBlock(BaseModel):
516
481
  type: Literal[BlockType.PDF] = BlockType.PDF
517
- pdf: PdfData
482
+ pdf: FileWithCaption
518
483
 
519
484
 
520
485
  # ============================================================================
@@ -669,31 +634,21 @@ class CreateToggleBlock(BaseModel):
669
634
  # ============================================================================
670
635
 
671
636
 
672
- class VideoData(BaseModel):
673
- model_config = ConfigDict(from_attributes=True)
674
- caption: list[RichText] = Field(default_factory=list)
675
- type: FileType
676
- external: ExternalFile | None = None
677
- file: NotionHostedFile | None = None
678
- file_upload: FileUploadFile | None = None
679
- name: str | None = None
680
-
681
-
682
637
  class VideoBlock(BaseBlock):
683
638
  type: Literal[BlockType.VIDEO] = BlockType.VIDEO
684
- video: VideoData
639
+ video: FileWithCaption
685
640
 
686
641
 
687
642
  class CreateVideoBlock(BaseModel):
688
643
  type: Literal[BlockType.VIDEO] = BlockType.VIDEO
689
- video: VideoData
644
+ video: FileWithCaption
690
645
 
691
646
 
692
647
  # ============================================================================
693
648
  # Block Union Type
694
649
  # ============================================================================
695
650
 
696
- Block = Annotated[
651
+ type Block = Annotated[
697
652
  (
698
653
  AudioBlock
699
654
  | BookmarkBlock
@@ -743,7 +698,7 @@ class BlockChildrenResponse(BaseModel):
743
698
  request_id: str
744
699
 
745
700
 
746
- BlockCreatePayload = Annotated[
701
+ type BlockCreatePayload = Annotated[
747
702
  (
748
703
  CreateAudioBlock
749
704
  | CreateBookmarkBlock