notionary 0.2.28__py3-none-any.whl → 0.3.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 (149) hide show
  1. notionary/__init__.py +9 -2
  2. notionary/blocks/__init__.py +5 -0
  3. notionary/blocks/client.py +6 -4
  4. notionary/blocks/enums.py +28 -1
  5. notionary/blocks/rich_text/markdown_rich_text_converter.py +14 -0
  6. notionary/blocks/rich_text/models.py +14 -0
  7. notionary/blocks/rich_text/name_id_resolver/__init__.py +2 -0
  8. notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
  9. notionary/blocks/rich_text/rich_text_markdown_converter.py +12 -0
  10. notionary/blocks/rich_text/rich_text_patterns.py +3 -0
  11. notionary/blocks/schemas.py +42 -10
  12. notionary/comments/__init__.py +5 -0
  13. notionary/comments/client.py +7 -10
  14. notionary/comments/factory.py +4 -6
  15. notionary/data_source/http/data_source_instance_client.py +14 -4
  16. notionary/data_source/properties/{models.py → schemas.py} +4 -8
  17. notionary/data_source/query/__init__.py +9 -0
  18. notionary/data_source/query/builder.py +38 -10
  19. notionary/data_source/query/schema.py +13 -10
  20. notionary/data_source/query/validator.py +11 -11
  21. notionary/data_source/schema/registry.py +104 -0
  22. notionary/data_source/schema/service.py +136 -0
  23. notionary/data_source/schemas.py +1 -1
  24. notionary/data_source/service.py +29 -103
  25. notionary/database/service.py +17 -60
  26. notionary/exceptions/__init__.py +5 -1
  27. notionary/exceptions/block_parsing.py +21 -0
  28. notionary/exceptions/search.py +24 -0
  29. notionary/http/client.py +9 -10
  30. notionary/http/models.py +5 -4
  31. notionary/page/content/factory.py +10 -3
  32. notionary/page/content/markdown/builder.py +76 -154
  33. notionary/page/content/markdown/nodes/__init__.py +0 -2
  34. notionary/page/content/markdown/nodes/audio.py +1 -1
  35. notionary/page/content/markdown/nodes/base.py +1 -1
  36. notionary/page/content/markdown/nodes/bookmark.py +1 -1
  37. notionary/page/content/markdown/nodes/breadcrumb.py +1 -1
  38. notionary/page/content/markdown/nodes/bulleted_list.py +31 -8
  39. notionary/page/content/markdown/nodes/callout.py +12 -10
  40. notionary/page/content/markdown/nodes/code.py +3 -5
  41. notionary/page/content/markdown/nodes/columns.py +39 -21
  42. notionary/page/content/markdown/nodes/container.py +64 -0
  43. notionary/page/content/markdown/nodes/divider.py +1 -1
  44. notionary/page/content/markdown/nodes/embed.py +1 -1
  45. notionary/page/content/markdown/nodes/equation.py +1 -1
  46. notionary/page/content/markdown/nodes/file.py +1 -1
  47. notionary/page/content/markdown/nodes/heading.py +26 -6
  48. notionary/page/content/markdown/nodes/image.py +1 -1
  49. notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
  50. notionary/page/content/markdown/nodes/mixins/caption.py +1 -1
  51. notionary/page/content/markdown/nodes/numbered_list.py +28 -5
  52. notionary/page/content/markdown/nodes/paragraph.py +1 -1
  53. notionary/page/content/markdown/nodes/pdf.py +1 -1
  54. notionary/page/content/markdown/nodes/quote.py +17 -5
  55. notionary/page/content/markdown/nodes/space.py +1 -1
  56. notionary/page/content/markdown/nodes/table.py +1 -1
  57. notionary/page/content/markdown/nodes/table_of_contents.py +1 -1
  58. notionary/page/content/markdown/nodes/todo.py +23 -7
  59. notionary/page/content/markdown/nodes/toggle.py +13 -14
  60. notionary/page/content/markdown/nodes/video.py +1 -1
  61. notionary/page/content/parser/context.py +98 -21
  62. notionary/page/content/parser/factory.py +1 -10
  63. notionary/page/content/parser/parsers/__init__.py +0 -2
  64. notionary/page/content/parser/parsers/audio.py +1 -1
  65. notionary/page/content/parser/parsers/base.py +1 -1
  66. notionary/page/content/parser/parsers/bookmark.py +1 -1
  67. notionary/page/content/parser/parsers/breadcrumb.py +1 -1
  68. notionary/page/content/parser/parsers/bulleted_list.py +52 -8
  69. notionary/page/content/parser/parsers/callout.py +55 -84
  70. notionary/page/content/parser/parsers/caption.py +1 -1
  71. notionary/page/content/parser/parsers/code.py +5 -5
  72. notionary/page/content/parser/parsers/column.py +23 -64
  73. notionary/page/content/parser/parsers/column_list.py +45 -45
  74. notionary/page/content/parser/parsers/divider.py +1 -1
  75. notionary/page/content/parser/parsers/embed.py +1 -1
  76. notionary/page/content/parser/parsers/equation.py +1 -1
  77. notionary/page/content/parser/parsers/file.py +1 -1
  78. notionary/page/content/parser/parsers/heading.py +65 -8
  79. notionary/page/content/parser/parsers/image.py +1 -1
  80. notionary/page/content/parser/parsers/numbered_list.py +52 -8
  81. notionary/page/content/parser/parsers/paragraph.py +3 -2
  82. notionary/page/content/parser/parsers/pdf.py +1 -1
  83. notionary/page/content/parser/parsers/quote.py +75 -15
  84. notionary/page/content/parser/parsers/space.py +14 -8
  85. notionary/page/content/parser/parsers/table.py +1 -1
  86. notionary/page/content/parser/parsers/table_of_contents.py +1 -1
  87. notionary/page/content/parser/parsers/todo.py +57 -19
  88. notionary/page/content/parser/parsers/toggle.py +17 -74
  89. notionary/page/content/parser/parsers/video.py +1 -1
  90. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +6 -4
  91. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +43 -22
  92. notionary/page/content/parser/pre_processsing/handlers/__init__.py +4 -0
  93. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +108 -54
  94. notionary/page/content/parser/pre_processsing/handlers/indentation.py +86 -0
  95. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
  96. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +14 -7
  97. notionary/page/content/parser/service.py +9 -0
  98. notionary/page/content/renderer/context.py +5 -2
  99. notionary/page/content/renderer/factory.py +2 -11
  100. notionary/page/content/renderer/post_processing/handlers/__init__.py +2 -2
  101. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
  102. notionary/page/content/renderer/renderers/__init__.py +0 -2
  103. notionary/page/content/renderer/renderers/base.py +1 -1
  104. notionary/page/content/renderer/renderers/bulleted_list.py +1 -1
  105. notionary/page/content/renderer/renderers/callout.py +6 -21
  106. notionary/page/content/renderer/renderers/captioned_block.py +1 -1
  107. notionary/page/content/renderer/renderers/column.py +28 -19
  108. notionary/page/content/renderer/renderers/column_list.py +24 -11
  109. notionary/page/content/renderer/renderers/heading.py +53 -27
  110. notionary/page/content/renderer/renderers/numbered_list.py +6 -5
  111. notionary/page/content/renderer/renderers/quote.py +1 -1
  112. notionary/page/content/renderer/renderers/todo.py +1 -1
  113. notionary/page/content/renderer/renderers/toggle.py +6 -7
  114. notionary/page/content/service.py +4 -1
  115. notionary/page/content/syntax/__init__.py +4 -0
  116. notionary/page/content/syntax/grammar.py +10 -0
  117. notionary/page/content/syntax/models.py +0 -2
  118. notionary/page/content/syntax/{service.py → registry.py} +31 -91
  119. notionary/page/properties/client.py +3 -3
  120. notionary/page/properties/models.py +3 -2
  121. notionary/page/properties/service.py +18 -3
  122. notionary/page/service.py +22 -80
  123. notionary/shared/entity/service.py +94 -36
  124. notionary/shared/models/cover.py +1 -1
  125. notionary/shared/typings.py +3 -0
  126. notionary/user/base.py +60 -11
  127. notionary/user/factory.py +0 -0
  128. notionary/utils/decorators.py +122 -0
  129. notionary/utils/fuzzy.py +18 -6
  130. notionary/utils/mixins/logging.py +38 -27
  131. notionary/utils/pagination.py +70 -16
  132. notionary/workspace/__init__.py +2 -1
  133. notionary/workspace/client.py +4 -2
  134. notionary/workspace/query/__init__.py +3 -0
  135. notionary/workspace/query/builder.py +25 -1
  136. notionary/workspace/query/models.py +12 -3
  137. notionary/workspace/query/service.py +57 -32
  138. notionary/workspace/service.py +31 -21
  139. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/METADATA +35 -105
  140. notionary-0.3.1.dist-info/RECORD +211 -0
  141. notionary/page/content/markdown/nodes/toggleable_heading.py +0 -35
  142. notionary/page/content/parser/parsers/toggleable_heading.py +0 -150
  143. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +0 -62
  144. notionary/page/content/renderer/renderers/toggleable_heading.py +0 -78
  145. notionary/utils/async_retry.py +0 -39
  146. notionary/utils/singleton.py +0 -13
  147. notionary-0.2.28.dist-info/RECORD +0 -200
  148. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/WHEEL +0 -0
  149. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,13 @@
1
1
  import re
2
2
 
3
+ from notionary.page.content.syntax.grammar import MarkdownGrammar
3
4
  from notionary.page.content.syntax.models import SyntaxDefinition, SyntaxRegistryKey
4
5
 
5
6
 
6
7
  class SyntaxRegistry:
7
- MULTI_LINE_BLOCK_DELIMITER = ":::"
8
- TOGGLE_DELIMITER = "+++"
9
- TABLE_DELIMITER = "|"
8
+ def __init__(self, markdown_markdown_grammar: MarkdownGrammar | None = None) -> None:
9
+ self._markdown_grammar = markdown_markdown_grammar or MarkdownGrammar()
10
10
 
11
- def __init__(self) -> None:
12
11
  self._definitions: dict[SyntaxRegistryKey, SyntaxDefinition] = {}
13
12
  self._register_defaults()
14
13
 
@@ -140,8 +139,6 @@ class SyntaxRegistry:
140
139
  start_delimiter="[audio](",
141
140
  end_delimiter=")",
142
141
  regex_pattern=re.compile(r"\[audio\]\(([^)]+)\)"),
143
- is_multiline_block=False,
144
- is_inline=True,
145
142
  )
146
143
  self._definitions[SyntaxRegistryKey.AUDIO] = definition
147
144
 
@@ -150,8 +147,6 @@ class SyntaxRegistry:
150
147
  start_delimiter="[bookmark](",
151
148
  end_delimiter=")",
152
149
  regex_pattern=re.compile(r"\[bookmark\]\((https?://[^\s\"]+)\)"),
153
- is_multiline_block=False,
154
- is_inline=True,
155
150
  )
156
151
  self._definitions[SyntaxRegistryKey.BOOKMARK] = definition
157
152
 
@@ -160,8 +155,6 @@ class SyntaxRegistry:
160
155
  start_delimiter="[breadcrumb]",
161
156
  end_delimiter="",
162
157
  regex_pattern=re.compile(r"^\[breadcrumb\]\s*$", re.IGNORECASE),
163
- is_multiline_block=False,
164
- is_inline=False,
165
158
  )
166
159
  self._definitions[SyntaxRegistryKey.BREADCRUMB] = definition
167
160
 
@@ -170,21 +163,16 @@ class SyntaxRegistry:
170
163
  start_delimiter="- ",
171
164
  end_delimiter="",
172
165
  regex_pattern=re.compile(r"^(\s*)-\s+(?!\[[ xX]\])(.+)$"),
173
- is_multiline_block=False,
174
- is_inline=False,
175
166
  )
176
167
  self._definitions[SyntaxRegistryKey.BULLETED_LIST] = definition
177
168
 
178
169
  def _register_callout_syntax(self) -> None:
179
170
  definition = SyntaxDefinition(
180
- start_delimiter=f"{self.MULTI_LINE_BLOCK_DELIMITER} callout",
181
- end_delimiter=self.MULTI_LINE_BLOCK_DELIMITER,
171
+ start_delimiter="[callout]",
172
+ end_delimiter=")",
182
173
  regex_pattern=re.compile(
183
- rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*callout(?:\s+(\S+))?\s*$", re.IGNORECASE
174
+ r'\[callout\](?:\(([^")]+?)(?:\s+"([^"]+)")?\)|(?:\s+([^"\n]+?)(?:\s+"([^"]+)")?)(?:\n|$))'
184
175
  ),
185
- end_regex_pattern=re.compile(rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*$"),
186
- is_multiline_block=True,
187
- is_inline=False,
188
176
  )
189
177
  self._definitions[SyntaxRegistryKey.CALLOUT] = definition
190
178
 
@@ -195,33 +183,29 @@ class SyntaxRegistry:
195
183
  end_delimiter=code_delimiter,
196
184
  regex_pattern=re.compile("^" + re.escape(code_delimiter) + r"(\w*)\s*$"),
197
185
  end_regex_pattern=re.compile("^" + re.escape(code_delimiter) + r"\s*$"),
198
- is_multiline_block=False,
199
- is_inline=False,
200
186
  )
201
187
  self._definitions[SyntaxRegistryKey.CODE] = definition
202
188
 
203
189
  def _register_column_syntax(self) -> None:
190
+ delimiter = self._markdown_grammar.column_delimiter
204
191
  definition = SyntaxDefinition(
205
- start_delimiter=f"{self.MULTI_LINE_BLOCK_DELIMITER} column",
206
- end_delimiter=self.MULTI_LINE_BLOCK_DELIMITER,
192
+ start_delimiter=f"{delimiter} column",
193
+ end_delimiter=delimiter,
207
194
  regex_pattern=re.compile(
208
- rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*column(?:\s+(0?\.\d+|1(?:\.0?)?))??\s*$",
195
+ rf"^{re.escape(delimiter)}\s*column(?:\s+(0?\.\d+|1(?:\.0?)?))??\s*$",
209
196
  re.IGNORECASE | re.MULTILINE,
210
197
  ),
211
- end_regex_pattern=re.compile(rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*$", re.MULTILINE),
212
- is_multiline_block=True,
213
- is_inline=False,
198
+ end_regex_pattern=re.compile(rf"^{re.escape(delimiter)}\s*$", re.MULTILINE),
214
199
  )
215
200
  self._definitions[SyntaxRegistryKey.COLUMN] = definition
216
201
 
217
202
  def _register_column_list_syntax(self) -> None:
203
+ delimiter = self._markdown_grammar.column_delimiter
218
204
  definition = SyntaxDefinition(
219
- start_delimiter=f"{self.MULTI_LINE_BLOCK_DELIMITER} columns",
220
- end_delimiter=self.MULTI_LINE_BLOCK_DELIMITER,
221
- regex_pattern=re.compile(rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*columns?\s*$", re.IGNORECASE),
222
- end_regex_pattern=re.compile(rf"^{re.escape(self.MULTI_LINE_BLOCK_DELIMITER)}\s*$"),
223
- is_multiline_block=True,
224
- is_inline=False,
205
+ start_delimiter=f"{delimiter} columns",
206
+ end_delimiter=delimiter,
207
+ regex_pattern=re.compile(rf"^{re.escape(delimiter)}\s*columns?\s*$", re.IGNORECASE),
208
+ end_regex_pattern=re.compile(rf"^{re.escape(delimiter)}\s*$"),
225
209
  )
226
210
  self._definitions[SyntaxRegistryKey.COLUMN_LIST] = definition
227
211
 
@@ -230,8 +214,6 @@ class SyntaxRegistry:
230
214
  start_delimiter="---",
231
215
  end_delimiter="",
232
216
  regex_pattern=re.compile(r"^\s*-{3,}\s*$"),
233
- is_multiline_block=False,
234
- is_inline=False,
235
217
  )
236
218
  self._definitions[SyntaxRegistryKey.DIVIDER] = definition
237
219
 
@@ -240,8 +222,6 @@ class SyntaxRegistry:
240
222
  start_delimiter="[embed](",
241
223
  end_delimiter=")",
242
224
  regex_pattern=re.compile(r"\[embed\]\((https?://[^\s)]+)\)"),
243
- is_multiline_block=False,
244
- is_inline=True,
245
225
  )
246
226
  self._definitions[SyntaxRegistryKey.EMBED] = definition
247
227
 
@@ -250,8 +230,6 @@ class SyntaxRegistry:
250
230
  start_delimiter="$$",
251
231
  end_delimiter="$$",
252
232
  regex_pattern=re.compile(r"^\$\$\s*$"),
253
- is_multiline_block=False,
254
- is_inline=False,
255
233
  )
256
234
  self._definitions[SyntaxRegistryKey.EQUATION] = definition
257
235
 
@@ -260,8 +238,6 @@ class SyntaxRegistry:
260
238
  start_delimiter="[file](",
261
239
  end_delimiter=")",
262
240
  regex_pattern=re.compile(r"\[file\]\(([^)]+)\)"),
263
- is_multiline_block=False,
264
- is_inline=True,
265
241
  )
266
242
  self._definitions[SyntaxRegistryKey.FILE] = definition
267
243
 
@@ -270,8 +246,6 @@ class SyntaxRegistry:
270
246
  start_delimiter="# ",
271
247
  end_delimiter="",
272
248
  regex_pattern=re.compile(r"^#\s+(.+)$"),
273
- is_multiline_block=False,
274
- is_inline=False,
275
249
  )
276
250
  self._definitions[SyntaxRegistryKey.HEADING_1] = definition
277
251
 
@@ -280,8 +254,6 @@ class SyntaxRegistry:
280
254
  start_delimiter="## ",
281
255
  end_delimiter="",
282
256
  regex_pattern=re.compile(r"^#{2}\s+(.+)$"),
283
- is_multiline_block=False,
284
- is_inline=False,
285
257
  )
286
258
  self._definitions[SyntaxRegistryKey.HEADING_2] = definition
287
259
 
@@ -290,8 +262,6 @@ class SyntaxRegistry:
290
262
  start_delimiter="### ",
291
263
  end_delimiter="",
292
264
  regex_pattern=re.compile(r"^#{3}\s+(.+)$"),
293
- is_multiline_block=False,
294
- is_inline=False,
295
265
  )
296
266
  self._definitions[SyntaxRegistryKey.HEADING_3] = definition
297
267
 
@@ -300,8 +270,6 @@ class SyntaxRegistry:
300
270
  start_delimiter="[image](",
301
271
  end_delimiter=")",
302
272
  regex_pattern=re.compile(r"(?<!!)\[image\]\(([^)]+)\)"),
303
- is_multiline_block=False,
304
- is_inline=True,
305
273
  )
306
274
  self._definitions[SyntaxRegistryKey.IMAGE] = definition
307
275
 
@@ -310,8 +278,6 @@ class SyntaxRegistry:
310
278
  start_delimiter="1. ",
311
279
  end_delimiter="",
312
280
  regex_pattern=re.compile(r"^(\s*)(\d+)\.\s+(.+)$"),
313
- is_multiline_block=False,
314
- is_inline=False,
315
281
  )
316
282
  self._definitions[SyntaxRegistryKey.NUMBERED_LIST] = definition
317
283
 
@@ -320,8 +286,6 @@ class SyntaxRegistry:
320
286
  start_delimiter="[pdf](",
321
287
  end_delimiter=")",
322
288
  regex_pattern=re.compile(r"\[pdf\]\(([^)]+)\)"),
323
- is_multiline_block=False,
324
- is_inline=True,
325
289
  )
326
290
  self._definitions[SyntaxRegistryKey.PDF] = definition
327
291
 
@@ -330,32 +294,24 @@ class SyntaxRegistry:
330
294
  start_delimiter="> ",
331
295
  end_delimiter="",
332
296
  regex_pattern=re.compile(r"^>(?!>)\s*(.+)$"),
333
- is_multiline_block=False,
334
- is_inline=False,
335
297
  )
336
298
  self._definitions[SyntaxRegistryKey.QUOTE] = definition
337
299
 
338
300
  def _register_table_syntax(self) -> None:
301
+ delimiter = self._markdown_grammar.table_delimiter
339
302
  definition = SyntaxDefinition(
340
- start_delimiter=self.TABLE_DELIMITER,
303
+ start_delimiter=delimiter,
341
304
  end_delimiter="",
342
- regex_pattern=re.compile(
343
- rf"^\s*{re.escape(self.TABLE_DELIMITER)}(.+){re.escape(self.TABLE_DELIMITER)}\s*$"
344
- ),
345
- is_multiline_block=False,
346
- is_inline=False,
305
+ regex_pattern=re.compile(rf"^\s*{re.escape(delimiter)}(.+){re.escape(delimiter)}\s*$"),
347
306
  )
348
307
  self._definitions[SyntaxRegistryKey.TABLE] = definition
349
308
 
350
309
  def _register_table_row_syntax(self) -> None:
310
+ delimiter = self._markdown_grammar.table_delimiter
351
311
  definition = SyntaxDefinition(
352
- start_delimiter=self.TABLE_DELIMITER,
312
+ start_delimiter=delimiter,
353
313
  end_delimiter="",
354
- regex_pattern=re.compile(
355
- rf"^\s*{re.escape(self.TABLE_DELIMITER)}([\s\-:|]+){re.escape(self.TABLE_DELIMITER)}\s*$"
356
- ),
357
- is_multiline_block=False,
358
- is_inline=False,
314
+ regex_pattern=re.compile(rf"^\s*{re.escape(delimiter)}([\s\-:|]+){re.escape(delimiter)}\s*$"),
359
315
  )
360
316
  self._definitions[SyntaxRegistryKey.TABLE_ROW] = definition
361
317
 
@@ -364,8 +320,6 @@ class SyntaxRegistry:
364
320
  start_delimiter="[toc]",
365
321
  end_delimiter="",
366
322
  regex_pattern=re.compile(r"^\[toc\]$", re.IGNORECASE),
367
- is_multiline_block=False,
368
- is_inline=False,
369
323
  )
370
324
  self._definitions[SyntaxRegistryKey.TABLE_OF_CONTENTS] = definition
371
325
 
@@ -374,8 +328,6 @@ class SyntaxRegistry:
374
328
  start_delimiter="- [ ]",
375
329
  end_delimiter="",
376
330
  regex_pattern=re.compile(r"^\s*-\s+\[ \]\s+(.+)$"),
377
- is_multiline_block=False,
378
- is_inline=False,
379
331
  )
380
332
  self._definitions[SyntaxRegistryKey.TO_DO] = definition
381
333
 
@@ -384,31 +336,27 @@ class SyntaxRegistry:
384
336
  start_delimiter="- [x]",
385
337
  end_delimiter="",
386
338
  regex_pattern=re.compile(r"^\s*-\s+\[x\]\s+(.+)$", re.IGNORECASE),
387
- is_multiline_block=False,
388
- is_inline=False,
389
339
  )
390
340
  self._definitions[SyntaxRegistryKey.TO_DO_DONE] = definition
391
341
 
392
342
  def _register_toggle_syntax(self) -> None:
343
+ delimiter = self._markdown_grammar.toggle_delimiter
393
344
  definition = SyntaxDefinition(
394
- start_delimiter=self.TOGGLE_DELIMITER,
395
- end_delimiter=self.TOGGLE_DELIMITER,
396
- regex_pattern=re.compile(rf"^{re.escape(self.TOGGLE_DELIMITER)}\s+(.+)$"),
397
- end_regex_pattern=re.compile(rf"^{re.escape(self.TOGGLE_DELIMITER)}\s*$"),
398
- is_multiline_block=True,
399
- is_inline=False,
345
+ start_delimiter=delimiter,
346
+ end_delimiter=delimiter,
347
+ regex_pattern=re.compile(rf"^{re.escape(delimiter)}\s+(.+)$"),
348
+ end_regex_pattern=re.compile(rf"^{re.escape(delimiter)}\s*$"),
400
349
  )
401
350
  self._definitions[SyntaxRegistryKey.TOGGLE] = definition
402
351
 
403
352
  def _register_toggleable_heading_syntax(self) -> None:
404
- escaped_delimiter = re.escape(self.TOGGLE_DELIMITER)
353
+ delimiter = self._markdown_grammar.toggle_delimiter
354
+ escaped_delimiter = re.escape(delimiter)
405
355
  definition = SyntaxDefinition(
406
- start_delimiter=f"{self.TOGGLE_DELIMITER} #",
407
- end_delimiter=self.TOGGLE_DELIMITER,
356
+ start_delimiter=f"{delimiter} #",
357
+ end_delimiter=delimiter,
408
358
  regex_pattern=re.compile(rf"^{escaped_delimiter}\s*(?P<level>#{{1,3}})(?!#)\s*(.+)$", re.IGNORECASE),
409
359
  end_regex_pattern=re.compile(rf"^{escaped_delimiter}\s*$"),
410
- is_multiline_block=True,
411
- is_inline=False,
412
360
  )
413
361
  self._definitions[SyntaxRegistryKey.TOGGLEABLE_HEADING] = definition
414
362
 
@@ -417,8 +365,6 @@ class SyntaxRegistry:
417
365
  start_delimiter="[video](",
418
366
  end_delimiter=")",
419
367
  regex_pattern=re.compile(r"\[video\]\(([^)]+)\)"),
420
- is_multiline_block=False,
421
- is_inline=True,
422
368
  )
423
369
  self._definitions[SyntaxRegistryKey.VIDEO] = definition
424
370
 
@@ -427,8 +373,6 @@ class SyntaxRegistry:
427
373
  start_delimiter="[caption]",
428
374
  end_delimiter="",
429
375
  regex_pattern=re.compile(r"^\[caption\]\s+(\S.*)$"),
430
- is_multiline_block=False,
431
- is_inline=False,
432
376
  )
433
377
  self._definitions[SyntaxRegistryKey.CAPTION] = definition
434
378
 
@@ -437,8 +381,6 @@ class SyntaxRegistry:
437
381
  start_delimiter="[space]",
438
382
  end_delimiter="",
439
383
  regex_pattern=re.compile(r"^\[space\]\s*$"),
440
- is_multiline_block=False,
441
- is_inline=False,
442
384
  )
443
385
  self._definitions[SyntaxRegistryKey.SPACE] = definition
444
386
 
@@ -447,7 +389,5 @@ class SyntaxRegistry:
447
389
  start_delimiter="#",
448
390
  end_delimiter="",
449
391
  regex_pattern=re.compile(r"^(#{1,3})[ \t]+(.+)$"),
450
- is_multiline_block=False,
451
- is_inline=False,
452
392
  )
453
393
  self._definitions[SyntaxRegistryKey.HEADING] = definition
@@ -51,8 +51,8 @@ class PagePropertyHttpClient(NotionHttpClient):
51
51
 
52
52
  return await self.patch_page(update_dto)
53
53
 
54
- async def patch_title(self, title: str) -> NotionPageDto:
55
- return await self._patch_property("title", title, PageTitleProperty)
54
+ async def patch_title(self, property_name: str, title: str) -> NotionPageDto:
55
+ return await self._patch_property(property_name, title, PageTitleProperty)
56
56
 
57
57
  async def patch_rich_text_property(self, property_name: str, text: str) -> NotionPageDto:
58
58
  return await self._patch_property(property_name, text, PageRichTextProperty)
@@ -123,7 +123,7 @@ class PagePropertyHttpClient(NotionHttpClient):
123
123
  elif property_type == PageCheckboxProperty:
124
124
  return PageCheckboxProperty(id=property_id, checkbox=bool(value))
125
125
  elif property_type == PageSelectProperty:
126
- select_option = SelectOption(id="", name=str(value))
126
+ select_option = SelectOption(name=str(value))
127
127
  return PageSelectProperty(id=property_id, select=select_option)
128
128
  elif property_type == PageMultiSelectProperty:
129
129
  multi_select_options = [SelectOption(id="", name=str(item)) for item in value]
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
5
5
 
6
6
  from notionary.blocks.rich_text.models import RichText
7
7
  from notionary.shared.properties.type import PropertyType
8
+ from notionary.shared.typings import JsonDict
8
9
  from notionary.user.schemas import PersonUserResponseDto, UserResponseDto
9
10
 
10
11
  # ============================================================================
@@ -23,7 +24,7 @@ class StatusOption(BaseModel):
23
24
 
24
25
 
25
26
  class SelectOption(BaseModel):
26
- id: str
27
+ id: str | None = None
27
28
  name: str
28
29
 
29
30
 
@@ -269,7 +270,7 @@ class PageVerificationProperty(PageProperty):
269
270
 
270
271
  class PageButtonProperty(PageProperty):
271
272
  type: Literal[PropertyType.BUTTON] = PropertyType.BUTTON
272
- button: dict[str, Any] = Field(default_factory=dict)
273
+ button: JsonDict = Field(default_factory=dict)
273
274
 
274
275
 
275
276
  # ============================================================================
@@ -139,15 +139,30 @@ class PagePropertyHandler:
139
139
  data_source = await self._get_parent_data_source_or_raise()
140
140
  return await data_source.get_options_for_property_by_name(property_name)
141
141
 
142
+ async def get_schema_description(self, property_name: str) -> str:
143
+ data_source = await self._get_parent_data_source_or_raise()
144
+ return await data_source.get_schema_description(property_name)
145
+
142
146
  # =========================================================================
143
147
  # Writer Methods
144
148
  # =========================================================================
145
149
 
146
- async def set_title_property(self, property_name: str, title: str) -> None:
147
- self._get_typed_property_or_raise(property_name, PageTitleProperty)
148
- updated_page = await self._property_http_client.patch_title(property_name, title)
150
+ async def set_title_property(self, title: str) -> None:
151
+ title_property_name = self._extract_title_property_name()
152
+
153
+ self._get_typed_property_or_raise(title_property_name, PageTitleProperty)
154
+ updated_page = await self._property_http_client.patch_title(title_property_name, title)
149
155
  self._properties = updated_page.properties
150
156
 
157
+ def _extract_title_property_name(self) -> str | None:
158
+ if not self._properties:
159
+ return None
160
+
161
+ return next(
162
+ (key for key, prop in self._properties.items() if isinstance(prop, PageTitleProperty)),
163
+ None,
164
+ )
165
+
151
166
  async def set_rich_text_property(self, property_name: str, text: str) -> None:
152
167
  self._get_typed_property_or_raise(property_name, PageRichTextProperty)
153
168
  updated_page = await self._property_http_client.patch_rich_text_property(property_name, text)
notionary/page/service.py CHANGED
@@ -14,59 +14,31 @@ from notionary.page.properties.factory import PagePropertyHandlerFactory
14
14
  from notionary.page.properties.models import PageTitleProperty
15
15
  from notionary.page.properties.service import PagePropertyHandler
16
16
  from notionary.page.schemas import NotionPageDto
17
- from notionary.shared.entity.dto_parsers import (
18
- extract_cover_image_url_from_dto,
19
- extract_emoji_icon_from_dto,
20
- extract_external_icon_url_from_dto,
21
- )
22
17
  from notionary.shared.entity.service import Entity
23
- from notionary.user.schemas import PartialUserDto
24
18
  from notionary.workspace.query.service import WorkspaceQueryService
25
19
 
26
20
 
27
21
  class NotionPage(Entity):
28
22
  def __init__(
29
23
  self,
30
- id: str,
24
+ dto: NotionPageDto,
31
25
  title: str,
32
- created_time: str,
33
- created_by: PartialUserDto,
34
- last_edited_time: str,
35
- last_edited_by: PartialUserDto,
36
- url: str,
37
- archived: bool,
38
- in_trash: bool,
39
26
  page_property_handler: PagePropertyHandler,
40
27
  block_client: NotionBlockHttpClient,
41
28
  comment_service: CommentService,
42
29
  page_content_service: PageContentService,
43
30
  metadata_update_client: PageMetadataUpdateClient,
44
- public_url: str | None = None,
45
- emoji_icon: str | None = None,
46
- external_icon_url: str | None = None,
47
- cover_image_url: str | None = None,
48
31
  ) -> None:
49
- super().__init__(
50
- id=id,
51
- created_time=created_time,
52
- created_by=created_by,
53
- last_edited_time=last_edited_time,
54
- last_edited_by=last_edited_by,
55
- in_trash=in_trash,
56
- emoji_icon=emoji_icon,
57
- external_icon_url=external_icon_url,
58
- cover_image_url=cover_image_url,
59
- )
32
+ super().__init__(dto=dto)
33
+
60
34
  self._title = title
61
- self._archived = archived
62
- self._url = url
63
- self._public_url = public_url
35
+ self._archived = dto.archived
64
36
 
65
37
  self._block_client = block_client
66
38
  self._comment_service = comment_service
67
39
  self._page_content_service = page_content_service
68
- self.properties = page_property_handler
69
40
  self._metadata_update_client = metadata_update_client
41
+ self.properties = page_property_handler
70
42
 
71
43
  @classmethod
72
44
  async def from_id(
@@ -75,18 +47,17 @@ class NotionPage(Entity):
75
47
  page_property_handler_factory: PagePropertyHandlerFactory | None = None,
76
48
  ) -> Self:
77
49
  factory = page_property_handler_factory or PagePropertyHandlerFactory()
78
- response = await cls._fetch_page_dto(page_id)
79
- return await cls._create_from_dto(response, factory)
50
+ dto = await cls._fetch_page_dto(page_id)
51
+ return await cls._create_from_dto(dto, factory)
80
52
 
81
53
  @classmethod
82
54
  async def from_title(
83
55
  cls,
84
56
  page_title: str,
85
- min_similarity: float = 0.6,
86
57
  search_service: WorkspaceQueryService | None = None,
87
58
  ) -> Self:
88
59
  service = search_service or WorkspaceQueryService()
89
- return await service.find_page(page_title, min_similarity=min_similarity)
60
+ return await service.find_page(page_title)
90
61
 
91
62
  @classmethod
92
63
  async def _fetch_page_dto(cls, page_id: str) -> NotionPageDto:
@@ -96,76 +67,43 @@ class NotionPage(Entity):
96
67
  @classmethod
97
68
  async def _create_from_dto(
98
69
  cls,
99
- response: NotionPageDto,
70
+ dto: NotionPageDto,
100
71
  page_property_handler_factory: PagePropertyHandlerFactory,
101
72
  ) -> Self:
102
- title_task = cls._extract_title_from_dto(response)
103
- page_property_handler = page_property_handler_factory.create_from_page_response(response)
73
+ title_task = cls._extract_title_from_dto(dto)
74
+ page_property_handler = page_property_handler_factory.create_from_page_response(dto)
104
75
 
105
76
  title = await title_task
106
77
 
107
78
  return cls._create_with_dependencies(
108
- id=response.id,
79
+ dto=dto,
109
80
  title=title,
110
- created_time=response.created_time,
111
- created_by=response.created_by,
112
- last_edited_time=response.last_edited_time,
113
- last_edited_by=response.last_edited_by,
114
- archived=response.archived,
115
- in_trash=response.in_trash,
116
- url=response.url,
117
81
  page_property_handler=page_property_handler,
118
- public_url=response.public_url,
119
- emoji_icon=extract_emoji_icon_from_dto(response),
120
- external_icon_url=extract_external_icon_url_from_dto(response),
121
- cover_image_url=extract_cover_image_url_from_dto(response),
122
82
  )
123
83
 
124
84
  @classmethod
125
85
  def _create_with_dependencies(
126
86
  cls,
127
- id: str,
87
+ dto: NotionPageDto,
128
88
  title: str,
129
- created_time: str,
130
- created_by: PartialUserDto,
131
- last_edited_time: str,
132
- last_edited_by: PartialUserDto,
133
- url: str,
134
- archived: bool,
135
- in_trash: bool,
136
89
  page_property_handler: PagePropertyHandler,
137
- public_url: str | None = None,
138
- emoji_icon: str | None = None,
139
- external_icon_url: str | None = None,
140
- cover_image_url: str | None = None,
141
90
  ) -> Self:
142
91
  block_client = NotionBlockHttpClient()
143
92
  comment_service = CommentService()
144
93
 
145
94
  page_content_service_factory = PageContentServiceFactory()
146
- page_content_service = page_content_service_factory.create(page_id=id, block_client=block_client)
95
+ page_content_service = page_content_service_factory.create(page_id=dto.id, block_client=block_client)
147
96
 
148
- metadata_update_client = PageMetadataUpdateClient(page_id=id)
97
+ metadata_update_client = PageMetadataUpdateClient(page_id=dto.id)
149
98
 
150
99
  return cls(
151
- id=id,
100
+ dto=dto,
152
101
  title=title,
153
- created_time=created_time,
154
- created_by=created_by,
155
- last_edited_time=last_edited_time,
156
- last_edited_by=last_edited_by,
157
- url=url,
158
- archived=archived,
159
- in_trash=in_trash,
160
102
  page_property_handler=page_property_handler,
161
103
  block_client=block_client,
162
104
  comment_service=comment_service,
163
105
  page_content_service=page_content_service,
164
106
  metadata_update_client=metadata_update_client,
165
- public_url=public_url,
166
- emoji_icon=emoji_icon,
167
- external_icon_url=external_icon_url,
168
- cover_image_url=cover_image_url,
169
107
  )
170
108
 
171
109
  @staticmethod
@@ -186,8 +124,12 @@ class NotionPage(Entity):
186
124
  return self._title
187
125
 
188
126
  @property
189
- def url(self) -> str:
190
- return self._url
127
+ def archived(self) -> bool:
128
+ return self._archived
129
+
130
+ @property
131
+ def markdown_builder() -> MarkdownBuilder:
132
+ return MarkdownBuilder()
191
133
 
192
134
  async def get_comments(self) -> list[Comment]:
193
135
  return await self._comment_service.list_all_comments_for_page(page_id=self._id)